Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions 2016-04-25_diagnostics_summary.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Diagnostics summary
===================

Total N (FIMS, cm^-3): 847.085
Total N (pop, cm^-3): 847.085
Ratio (pop/FIMS): 1

AMS mass fractions (observed, normalized over available keys):
SO4: 0.3376
NO3: 0.0352
OC: 0.5160
NH4: 0.1112

AMS mass fractions (reconstructed, normalized over available keys):
SO4: 0.5251
NO3: 0.0085
OC: 0.1477
NH4: 0.3187

miniSPLAT number fractions (observed, normalized):
AS: 0.6115
BB: 0.2991
BC: 0.0409
IEPOX: 0.0414
OIN: 0.0072

miniSPLAT number fractions (reconstructed / builder metadata, normalized):
AS: 0.6115
BB: 0.2991
BC: 0.0409
IEPOX: 0.0414
OIN: 0.0072
11 changes: 11 additions & 0 deletions README.pypi.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ print(pop)

More examples are available in the project repository.

## Interactive viewer

An experimental Streamlit UI lets you build populations through the factory registries and render existing visualization builders.

```bash
pip install -e .
streamlit run scripts/launch_viewer.py
```

The viewer source lives under `viewer/`. The sidebar lists every registered population and plot type; choose any combination, adjust the metadata-driven controls, and the figure plus diagnostics render inline.

## Contributing

`part2pop` is designed so that **all extensibility happens through factories**.
Expand Down
44 changes: 44 additions & 0 deletions diagnostics_summary.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
Diagnostics summary
===================

Fitted size distribution (Dpg_nm, sigma):
Mode 0: 73.9811, 1.682
Mode 1: 207.28, 1.357

Optimized fraction of particles in each mode:
OC: 0.209 0.791
SO4: 0.569 0.431
NO3: 0.853 0.147
IEPOX_SOA: 0.239 0.761

Total N (FIMS, cm^-3): 1582.01
Total N (pop, cm^-3): 1707.48
Ratio (pop/FIMS): 1.07931

AMS mass fractions (observed, normalized over available keys):
OC: 0.4764
NO3: 0.0670
SO4: 0.3183
NH4: 0.1383

AMS mass fractions (reconstructed, normalized over available keys):
OC: 0.5267
NO3: 0.0481
SO4: 0.3226
NH4: 0.1026

miniSPLAT number fractions (observed, normalized):
BC: 0.0108
OIN: 0.0042
OC: 0.4683
SO4: 0.3496
NO3: 0.1454
IEPOX_SOA: 0.0217

miniSPLAT number fractions (reconstructed, normalized):
BC: 0.0108
OIN: 0.0042
OC: 0.4683
SO4: 0.3496
NO3: 0.1454
IEPOX_SOA: 0.0217
16 changes: 16 additions & 0 deletions environment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: part2pop
channels:
- conda-forge
dependencies:
- python>=3.10
- numpy
- scipy
- matplotlib
- netCDF4
- importlib-resources
- pymiescatt
- tqdm
- streamlit
- pip
- pip:
- -r requirements.txt
Binary file removed examples/.DS_Store
Binary file not shown.
100 changes: 100 additions & 0 deletions examples/example_data/hiscale_population_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"type": "hiscale_observations",
"aimms_file": "/Users/fier887/Library/CloudStorage/OneDrive-PNNL/Code/multipart_archived2/multipart_archived/separate_tools/datasets/HISCALE_data_0425/AIMMS20_G1_20160425155810_R2_HISCALE020h.txt",
"splat_file": "/Users/fier887/Library/CloudStorage/OneDrive-PNNL/Code/multipart_archived2/multipart_archived/separate_tools/datasets/HISCALE_data_0425/Splat_Composition_25-Apr-2016.txt",
"ams_file": "/Users/fier887/Library/CloudStorage/OneDrive-PNNL/Code/multipart_archived2/multipart_archived/separate_tools/datasets/HISCALE_data_0425/HiScaleAMS_G1_20160425_R0.txt",
"fims_file": "/Users/fier887/Library/CloudStorage/OneDrive-PNNL/Code/multipart_archived2/multipart_archived/separate_tools/datasets/HISCALE_data_0425/FIMS_G1_20160425_R1_HISCALE_001s.txt",
"fims_bins_file": "/Users/fier887/Library/CloudStorage/OneDrive-PNNL/Code/multipart_archived2/multipart_archived/separate_tools/datasets/HISCALE_data_0425/HISCALE_FIMS_bins_R1.txt",
"z": 1000.0,
"dz": 100.0,
"splat_species": {
"BC": [
"soot"
],
"OIN": [
"Dust"
],
"OC": [
"org28",
"org30_43",
"BB_SOA",
"org_amines",
"BB",
"pyridine"
],
"SO4": [
"sulfate_nitrate_org"
],
"NO3": [
"nitrate_amine_org"
],
"IEPOX_SOA": [
"IEPOX_SOA"
]
},
"mass_thresholds": {
"IEPOX_SOA": [
[
0.3,
0.5,
0.1
],
[
"IEPOX_OS",
"tetrol",
"tetrol_olig",
"IEPOX_OH_SOA"
]
],
"SO4": [
[
0.5,
0.7,
0.1
],
[
"SO4"
]
],
"NO3": [
[
0.5,
0.7,
0.1
],
[
"NO3"
]
],
"OC": [
[
0.5,
0.7,
0.1
],
[
"OC"
]
],
"BC": [
[
0.5,
0.7,
0.1
],
[
"BC"
]
],
"OIN": [
[
0.5,
0.7,
0.1
],
[
"OIN"
]
]
}
}
5 changes: 5 additions & 0 deletions launch_viewer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from viewer import run_viewer


if __name__ == "__main__":
run_viewer()
11 changes: 11 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
numpy
scipy
matplotlib
netCDF4
importlib-resources
PyMieScatt
pyBCabs
tqdm
streamlit
pytest>=7
pytest-cov>=4
19 changes: 19 additions & 0 deletions scripts/launch_viewer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Entry point for running the Streamlit viewer in a source checkout."""

from __future__ import annotations

from pathlib import Path
import sys

REPO_ROOT = Path(__file__).resolve().parents[1]
SRC_DIR = REPO_ROOT / "src"
if SRC_DIR.is_dir() and str(SRC_DIR) not in sys.path:
sys.path.insert(0, str(SRC_DIR))
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))

from viewer import run_viewer


if __name__ == "__main__":
run_viewer()
8 changes: 6 additions & 2 deletions src/part2pop/analysis/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@
"T": 298.15,
},

# relative-humidity axis
# temperature / humidity axes
"T_grid": {
"T_grid": np.asarray([298.15]),
"T": 298.15,
"T_units": "K",
},
"rh_grid": {"rh_grid": np.asarray([0.0])},

# other/legacy entries (keep for compatibility)
Expand Down Expand Up @@ -110,4 +115,3 @@ def get_defaults_for_variable(name: str) -> Dict[str, Any]:
def all_defaults() -> Dict[str, Dict[str, Any]]:
"""Return a copy of the whole defaults mapping (diagnostic)."""
return {k: dict(v) for k, v in _DEFAULTS_BY_VAR.items()}

27 changes: 21 additions & 6 deletions src/part2pop/analysis/distributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

import numpy as np

_TRAPEZOID_FUNC = getattr(np, "trapezoid", None)
_TRAPZ_FUNC = getattr(np, "trapz", None)

if _TRAPZ_FUNC is None and _TRAPEZOID_FUNC is not None:
_TRAPZ_FUNC = _TRAPEZOID_FUNC
np.trapz = _TRAPZ_FUNC

if _TRAPEZOID_FUNC is None and _TRAPZ_FUNC is not None:
_TRAPEZOID_FUNC = _TRAPZ_FUNC
np.trapezoid = _TRAPEZOID_FUNC

def _trapezoid_integrate(y, x=None, dx=1.0, axis=-1):
"""
Expand All @@ -11,12 +21,17 @@ def _trapezoid_integrate(y, x=None, dx=1.0, axis=-1):
releases only expose `np.trapz`. This helper always calls whichever
function exists so the rest of the module can rely on a single name.
"""
func = getattr(np, "trapezoid", None)
if func is None:
func = getattr(np, "trapz", None)
if func is None:
raise AttributeError("NumPy installation lacks trapezoid/trapz integrators")
return func(y, x=x, dx=dx, axis=axis)
if _TRAPEZOID_FUNC is not None:
return _TRAPEZOID_FUNC(y, x=x, dx=dx, axis=axis)
if _TRAPZ_FUNC is not None:
return _TRAPZ_FUNC(y, x=x, dx=dx, axis=axis)
raise AttributeError("NumPy installation lacks trapezoid/trapz integrators")
# func = getattr(np, "trapezoid", None)
# if func is None:
# func = getattr(np, "trapz", None)
# if func is None:
# raise AttributeError("NumPy installation lacks trapezoid/trapz integrators")
# return func(y, x=x, dx=dx, axis=axis)

try:
from scipy.interpolate import PchipInterpolator as _PCHIP
Expand Down
16 changes: 13 additions & 3 deletions src/part2pop/analysis/particle/factory/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,23 @@ def describe_particle_variable(name: str):
if not meta:
raise UnknownParticleVariableError(name, suggestions=None)

axis_names = meta.axis_names
if isinstance(axis_names, str):
axis_names = [axis_names]
else:
axis_names = list(axis_names)

units = getattr(meta, "units", None)
if isinstance(units, dict):
units = dict(units)

return {
"name": meta.name,
"value_key": meta.name,
"axis_keys": list(meta.axis_names),
"axis_keys": list(axis_names),
"axis_names": list(axis_names),
"description": meta.description,
"aliases": list(meta.aliases),
"defaults": dict(meta.default_cfg),
"units": dict(meta.units) if getattr(meta, "units", None) else None,
"units": units,
}

4 changes: 2 additions & 2 deletions src/part2pop/analysis/population/factory/dNdlnD.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def compute(self, population, as_dict: bool = False):
- The variable is *always* defined w.r.t. ln(D), i.e. measure="ln".
"""
cfg = self.cfg
method = cfg.get("method", "hist")
method = cfg.get("method", "kde")
measure = "ln" # this variable is per dlnD by definition

# ------------------------------------------------------------------
Expand Down Expand Up @@ -201,7 +201,7 @@ def build(cfg=None) -> DNdlnDVar:
Factory function used by the analysis population registry.

Config keys (common ones):
- "method": "hist" (default) or "kde"
- "method": "kde" (default) or "hist"
- "wetsize": bool (True = use wet diameters, False = dry)
- "N_bins": int
- "D_min", "D_max": floats in meters
Expand Down
7 changes: 5 additions & 2 deletions src/part2pop/population/factory/hiscale_observations.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,12 +288,12 @@ def _read_fims_bins_file(fims_bins_file: str, expected_bins: int) -> Tuple[np.nd
Parse a bins file and return (Dp_lowers_nm, Dp_uppers_nm) with length expected_bins.
Works for:
- 2-column tables (lower upper)
- 3-column tables (min, mean, max) where we use first/last columns
- numeric streams that are interleaved lo,hi,lo,hi...
- numeric streams that are concatenated all-lo then all-hi
"""
lines = _read_lines(fims_bins_file)

# Try per-line 2-col parse first
lo_list: List[float] = []
hi_list: List[float] = []
for line in lines:
Expand All @@ -303,7 +303,10 @@ def _read_fims_bins_file(fims_bins_file: str, expected_bins: int) -> Tuple[np.nd
vals = re.findall(r"[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?", s)
if len(vals) >= 2:
lo_list.append(float(vals[0]))
hi_list.append(float(vals[1]))
hi_list.append(float(vals[-1]))
if len(vals) >= 3 and vals[0] != vals[-1]:
lo_list[-1] = float(vals[0])
hi_list[-1] = float(vals[-1])
if len(lo_list) == expected_bins and len(hi_list) == expected_bins:
lo = np.array(lo_list, dtype="float64")
hi = np.array(hi_list, dtype="float64")
Expand Down
Loading
Loading