Skip to content

Commit f8b1c75

Browse files
committed
Add arithemtic std as a cli param
1 parent fc07e1e commit f8b1c75

File tree

6 files changed

+121
-16
lines changed

6 files changed

+121
-16
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
.envrc
22
.direnv
33
RESULTS/
4+
*.speedscope.json
45

56
*.csv
67

docs/source/usage.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ pyfracval -n 512 --df 1.9 --kf 1.4 --rp-g 50 --rp-gstd 1.25
3030
- `--df`: Target fractal dimension.
3131
- `--kf`: Target fractal prefactor.
3232
- `--rp-g`: Geometric mean radius of primary particles.
33-
- `--rp-gstd`: Geometric standard deviation of radii (`1.0` for monodisperse).
33+
- `--rp-gstd`: **Geometric** standard deviation of radii (`>= 1.0`). If provided, **takes precedence** over `--rp-std`. If neither is provided, defaults to [Default Value, e.g., 1.5].
34+
- `--rp-std`: Approximate **arithmetic** standard deviation of radii. Used to _estimate_ `--rp-gstd` via `exp(std/mean)` **only if `--rp-gstd` is not given**. A warning will show the estimated geometric value being used.
3435
- `--tol-ov`: Overlap tolerance (e.g., `1e-5`).
3536
- `--n-subcl-perc`: Target fraction for PCA subcluster size (e.g., `0.1`).
3637
- `--num-aggregates`: Generate multiple aggregates sequentially.
@@ -41,6 +42,25 @@ pyfracval -n 512 --df 1.9 --kf 1.4 --rp-g 50 --rp-gstd 1.25
4142
- `--log-file`: Redirect log output to a file.
4243
- `-h`, `--help`: Show all available options and their defaults.
4344

45+
**Example: Using Arithmetic Standard Deviation**
46+
47+
Generate an aggregate with N=200, Df=1.9, kf=1.2, geometric mean radius 20, and an approximate _arithmetic_ standard deviation of 5. `pyfracval` will estimate the geometric standard deviation and use that.
48+
49+
```bash
50+
pyfracval -n 200 --df 1.9 --kf 1.2 --rp-g 20 --rp-std 5 -vv
51+
```
52+
53+
_(Check the log output for a WARNING indicating the calculated `rp_gstd` value being used)._
54+
55+
**Example: Geometric Standard Deviation Takes Precedence**
56+
57+
If you provide both, `--rp-gstd` will be used:
58+
59+
```bash
60+
# rp_gstd=1.3 will be used, rp-std=5 will be ignored (with a warning)
61+
pyfracval -n 100 --df 1.8 --rp-gstd 1.3 --rp-std 5
62+
```
63+
4464
**Example: Generating Multiple Aggregates with Plotting**
4565

4666
Generate 3 aggregates, each with N=100, Df=1.7, kf=1.1, and show the plots:

pyfracval/app.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
from pathlib import Path
66

77
import numpy as np
8-
import polars as pl
8+
9+
# import polars as pl
10+
import pandas as pd
911
import pyvista as pv
1012
import streamlit as st
1113
from stpyvista import stpyvista
@@ -49,7 +51,7 @@
4951

5052
match Path(file).suffix:
5153
case ".csv":
52-
data = pl.read_csv(file).to_numpy()
54+
data = pd.read_csv(file).to_numpy()
5355
information = re.search(
5456
r"N(\d+)-D(\d+_\d+)-K(\d+_\d+)-(\d+)_(\d+)_(\d+)",
5557
str(file),
@@ -76,8 +78,28 @@
7678
case _:
7779
st.error("File type not supported")
7880

79-
pl = plot_particles(data[:, :3], data[:, 3])
80-
stpyvista(pl)
81+
plotter = plot_particles(data[:, :3], data[:, 3])
82+
stpyvista(plotter)
83+
84+
gmean = np.exp(np.mean(np.log(data[:, 3])))
85+
gstd = np.exp(np.std(np.log(data[:, 3])))
86+
87+
col1, col2 = st.columns([1, 4])
88+
with col1:
89+
st.table(
90+
pd.DataFrame(
91+
dict(
92+
Arithemtic=[np.mean(data[:, 3]), np.std(data[:, 3])],
93+
Geometric=[gmean, gstd],
94+
),
95+
index=["Mean", "STD"], # pyright: ignore
96+
)
97+
)
98+
with col2:
99+
st.write(
100+
"Approximate Geometric STD: ",
101+
np.exp(np.std(data[:, 3]) / np.mean(data[:, 3])),
102+
)
81103

82104
st.write(metadata)
83105
with st.expander("Full file path"):

pyfracval/cli.py

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pathlib import Path
55

66
import click
7+
import numpy as np
78

89
from pyfracval import config as default_config
910
from pyfracval.logs import TRACE_LEVEL_NUM, create_logger
@@ -65,9 +66,19 @@
6566
@click.option(
6667
"--rp-gstd",
6768
type=float,
68-
default=DEFAULT_SIGMA,
69-
show_default=True,
70-
help="Geometric standard deviation of primary particle radii (>= 1.0).",
69+
default=None,
70+
show_default=f"Calculated from --rp-std or defaults to {DEFAULT_SIGMA}", # Show calculated default
71+
help="Geometric standard deviation of primary particle radii (>= 1.0). "
72+
"If provided, this value takes precedence over --rp-std.", # Added precedence info
73+
)
74+
@click.option(
75+
"--rp-std",
76+
type=float,
77+
default=None,
78+
help="Approximate arithmetic standard deviation of primary particle radii. "
79+
"If --rp-gstd is NOT provided, this value will be used to estimate "
80+
"a geometric standard deviation using the heuristic exp(std/mean). "
81+
"A warning will be shown with the calculated geometric value.",
7182
)
7283
@click.option(
7384
"--ext-case",
@@ -145,6 +156,9 @@ def cli(ctx, **kwargs) -> None:
145156
by Cluster-Cluster Aggregation (CCA) approach described by
146157
Moran et al. (2019) to generate aggregates with tunable fractal
147158
dimension (Df) and prefactor (kf) from polydisperse primary particles.
159+
160+
Allows specifying target Df, kf, and particle size distribution
161+
(via geometric mean/std dev or estimated from arithmetic std dev).
148162
"""
149163
if ctx.invoked_subcommand:
150164
return
@@ -162,11 +176,6 @@ def cli(ctx, **kwargs) -> None:
162176
logger = create_logger(log_level, kwargs["log_file"])
163177

164178
# --- Validate Inputs ---
165-
if kwargs["rp_gstd"] < 1.0:
166-
raise click.BadParameter(
167-
"Geometric standard deviation (rp_gstd) must be >= 1.0.",
168-
param_hint="--rp-gstd",
169-
)
170179
if kwargs["rp_g"] <= 0:
171180
raise click.BadParameter(
172181
"Geometric mean radius (rp_g) must be > 0.", param_hint="--rp-g"
@@ -175,14 +184,45 @@ def cli(ctx, **kwargs) -> None:
175184
raise click.BadParameter(
176185
"Number of particles (n) must be at least 2.", param_hint="-n"
177186
)
187+
if kwargs["rp_gstd"] is not None:
188+
# Geometric STD provided, use it directly (takes precedence)
189+
if kwargs["rp_gstd"] < 1.0:
190+
raise click.BadParameter(
191+
"Geometric standard deviation (--rp-gstd) must be >= 1.0.",
192+
param_hint="--rp-gstd",
193+
)
194+
final_rp_gstd = kwargs["rp_gstd"]
195+
if kwargs["rp_std"] is not None:
196+
logger.warning("Both --rp-gstd and --rp-std provided. Using --rp-gstd.")
197+
elif kwargs["rp_std"] is not None:
198+
# Arithmetic STD provided, Geometric STD not provided
199+
if kwargs["rp_std"] < 0:
200+
raise click.BadParameter(
201+
"Arithmetic standard deviation (--rp-std) cannot be negative.",
202+
param_hint="--rp-std",
203+
)
204+
# Apply heuristic: sigma_g = exp(sigma_a / mu_g)
205+
206+
final_rp_gstd = np.exp(kwargs["rp_std"] / kwargs["rp_g"])
207+
logger.warning(
208+
f"Using heuristic to calculate geometric standard deviation from arithmetic std: "
209+
f"exp(rp_std / rp_g) = exp({kwargs['rp_std']:.2f} / {kwargs['rp_g']:.2f}) = {final_rp_gstd:.3f}. "
210+
f"Targeting rp_gstd = {final_rp_gstd:.3f} for generation."
211+
)
212+
else:
213+
# Neither provided, use the default geometric STD
214+
final_rp_gstd = DEFAULT_SIGMA
215+
logger.info(
216+
f"No geometric or arithmetic standard deviation provided. Using default rp_gstd = {final_rp_gstd:.3f}"
217+
)
178218

179219
# --- Prepare Configuration for Runner ---
180220
sim_config = {
181221
"N": kwargs["num_particles"],
182222
"Df": kwargs["df"],
183223
"kf": kwargs["kf"],
184224
"rp_g": kwargs["rp_g"],
185-
"rp_gstd": kwargs["rp_gstd"],
225+
"rp_gstd": final_rp_gstd,
186226
"tol_ov": kwargs["tol_ov"],
187227
"n_subcl_percentage": kwargs["n_subcl_perc"],
188228
"ext_case": kwargs["ext_case"],

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ docs = [
3535
"sphinxcontrib-bibtex>=2.6.3",
3636
"sphinxcontrib-napoleon>=0.7",
3737
]
38-
test = ["pytest>=8.3.3"]
38+
test = [
39+
"py-spy>=0.4.0",
40+
"pytest>=8.3.3",
41+
]
3942

4043
[project.scripts]
4144
pyfracval = "pyfracval.cli:cli"

uv.lock

Lines changed: 20 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)