goad-py 0.5.1__tar.gz → 0.5.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of goad-py might be problematic. Click here for more details.
- {goad_py-0.5.1 → goad_py-0.5.5}/PKG-INFO +3 -1
- {goad_py-0.5.1 → goad_py-0.5.5}/config/default.toml +7 -15
- goad_py-0.5.5/goad-py/UNIFIED_API.md +307 -0
- goad_py-0.5.5/goad-py/UNIFIED_API_SUMMARY.md +224 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/convergence.py +244 -46
- goad_py-0.5.5/goad-py/examples/README.md +154 -0
- goad_py-0.5.5/goad-py/examples/direct/README.md +71 -0
- goad_py-0.5.5/goad-py/examples/direct/backscatter_convergence_example.py +140 -0
- goad_py-0.5.5/goad-py/examples/direct/ensemble_example.py +73 -0
- {goad_py-0.5.1/goad-py → goad_py-0.5.5/goad-py/examples/direct}/multiproblem_example.py +1 -1
- goad_py-0.5.5/goad-py/examples/direct/phips_convergence_example.py +211 -0
- goad_py-0.5.5/goad-py/examples/direct/phips_ensemble_convergence_example.py +231 -0
- goad_py-0.5.5/goad-py/examples/direct/s11_convergence_example.py +117 -0
- {goad_py-0.5.1/goad-py → goad_py-0.5.5/goad-py/examples/direct}/simple_example.py +1 -1
- goad_py-0.5.5/goad-py/examples/unified/01_simple_asymmetry.py +36 -0
- goad_py-0.5.5/goad-py/examples/unified/02_interval_binning.py +47 -0
- goad_py-0.5.5/goad-py/examples/unified/03_multiple_targets.py +35 -0
- goad_py-0.5.5/goad-py/examples/unified/04_mueller_element.py +37 -0
- goad_py-0.5.5/goad-py/examples/unified/05_backscatter_specific_bin.py +41 -0
- goad_py-0.5.5/goad-py/examples/unified/06_ensemble_convergence.py +36 -0
- goad_py-0.5.5/goad-py/examples/unified/07_advanced_config.py +55 -0
- goad_py-0.5.5/goad-py/examples/unified/08_parameter_sweep.py +57 -0
- goad_py-0.5.5/goad-py/examples/unified/09_phips_convergence.py +98 -0
- goad_py-0.5.5/goad-py/examples/unified/09_phips_convergence_results.json +220 -0
- goad_py-0.5.5/goad-py/examples/unified/README.md +85 -0
- goad_py-0.5.5/goad-py/python/goad_py/__init__.py +51 -0
- goad_py-0.5.5/goad-py/python/goad_py/convergence.py +882 -0
- goad_py-0.5.5/goad-py/python/goad_py/phips_convergence.py +580 -0
- goad_py-0.5.5/goad-py/python/goad_py/unified_convergence.py +1085 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/release.sh +19 -19
- {goad_py-0.5.1 → goad_py-0.5.5}/pyproject.toml +5 -2
- goad_py-0.5.5/python/goad_py/__init__.py +51 -0
- goad_py-0.5.5/python/goad_py/convergence.py +882 -0
- goad_py-0.5.5/python/goad_py/phips_convergence.py +580 -0
- goad_py-0.5.5/python/goad_py/unified_convergence.py +1085 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/bins.rs +48 -38
- {goad_py-0.5.1 → goad_py-0.5.5}/src/diff.rs +1 -3
- {goad_py-0.5.1 → goad_py-0.5.5}/src/result.rs +3 -1
- {goad_py-0.5.1 → goad_py-0.5.5}/src/settings.rs +144 -0
- goad_py-0.5.1/goad-py/python/goad_py/__init__.py +0 -7
- goad_py-0.5.1/goad-py/python/goad_py/convergence.py +0 -382
- goad_py-0.5.1/python/goad_py/__init__.py +0 -7
- goad_py-0.5.1/python/goad_py/convergence.py +0 -382
- {goad_py-0.5.1 → goad_py-0.5.5}/.github/workflows/python.yml +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/.github/workflows/rust.yml +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/.gitignore +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/Cargo.lock +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/Cargo.toml +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/LICENSE +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/README-python.md +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/README.md +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/blender/addon/__init__.py +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/blender/addon/goad_py/__init__.py +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/blender/addon.zip +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/blender/build.sh +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/blender/dev.blend +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/config_editor.sh +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/8col.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/clip_test.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/concave1.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/concave2.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cone.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cube.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cube2.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cube_inside_cube.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cube_inside_hex.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cube_inside_ico.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cubes.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex2.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex3.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex4.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex5.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex6.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex7.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex_20_30_30.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex_distort.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex_hollow.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex_indented.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/icosphere1.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/multiple.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/multiple2.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/multiple3.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/octo.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/octo2.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/para.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/para_rough1.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/plane_xy.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/plane_yz.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/plate.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/plate_distort.obj +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/multi-problem.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/problem-diff.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/problem1.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/examples/simplify.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/.gitignore +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/CLAUDE.md +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/Cargo.toml +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/DISTRIBUTION.md +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/README-python.md +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/build_and_test.sh +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/build_wheels_local.sh +0 -0
- {goad_py-0.5.1/goad-py → goad_py-0.5.5/goad-py/examples/direct}/convergence_example.py +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/goad_py.pyi +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/plot.ipynb +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/publish_test.sh +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/python/goad_py/goad_py.pyi +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/src/lib.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/test_wheels.sh +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/python/goad_py/goad_py.pyi +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/setup.sh +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/_quickstart.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/beam.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/clip.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/containment.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/distortion.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/field.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/fresnel.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/geom.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/lib.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/main.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/multiproblem.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/orientation.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/output.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/params.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/powers.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/problem.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/settings/cli.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/settings/constants.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/settings/loading.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/settings/validation.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/src/snell.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/template/custom_bins.toml +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/template/goad_pbs.sh +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/tests/fixed_orientation_tests.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/tests/helpers.rs +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/tests/test_data/fixed_hex_30_20_20_mueller_scatgrid +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/tests/test_data/fixed_hex_30_30_30_mueller_scatgrid +0 -0
- {goad_py-0.5.1 → goad_py-0.5.5}/tests/test_data/hex.obj +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: goad-py
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.5
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Science/Research
|
|
6
6
|
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
@@ -15,6 +15,8 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Classifier: Operating System :: OS Independent
|
|
18
|
+
Requires-Dist: numpy>=1.20.0
|
|
19
|
+
Requires-Dist: toml>=0.10.0
|
|
18
20
|
Summary: Physical optics light scattering computation
|
|
19
21
|
Keywords: light-scattering,optics,simulation,scientific-computing,physics
|
|
20
22
|
Home-Page: https://github.com/hballington12/goad
|
|
@@ -50,22 +50,14 @@ theta_spacings =[2]
|
|
|
50
50
|
phis = [0, 360]
|
|
51
51
|
phi_spacings = [4]
|
|
52
52
|
|
|
53
|
-
# [binning.scheme.Custom] # Custom binning with specified [
|
|
53
|
+
# [binning.scheme.Custom] # Custom binning with specified bin edges [[theta_min, theta_max], [phi_min, phi_max]]
|
|
54
54
|
# bins = [
|
|
55
|
-
# [
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
# ],
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
# 40,
|
|
62
|
-
# ],
|
|
63
|
-
# [
|
|
64
|
-
# 20,
|
|
65
|
-
# 180,
|
|
66
|
-
# ],
|
|
67
|
-
# ] # custom bins, unless a file is specified below
|
|
68
|
-
# file = "phips_bins.toml" # File containing custom bins, relative to working directory
|
|
55
|
+
# [[0.0, 10.0], [0.0, 90.0]], # First bin: theta 0-10°, phi 0-90°
|
|
56
|
+
# [[0.0, 10.0], [90.0, 180.0]], # Second bin: theta 0-10°, phi 90-180°
|
|
57
|
+
# [[10.0, 20.0], [0.0, 90.0]], # Third bin: theta 10-20°, phi 0-90°
|
|
58
|
+
# [[10.0, 20.0], [90.0, 180.0]], # Fourth bin: theta 10-20°, phi 90-180°
|
|
59
|
+
# ]
|
|
60
|
+
# file = "phips_bins_edges.toml" # Or load bins from external file (relative to working directory)
|
|
69
61
|
|
|
70
62
|
|
|
71
63
|
[orientation] # Orientation configuration
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# Unified Convergence API
|
|
2
|
+
|
|
3
|
+
A single, consistent interface for all GOAD convergence studies.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The unified convergence API provides a single entry point for running convergence studies with GOAD, supporting:
|
|
8
|
+
|
|
9
|
+
- **Standard mode**: Integrated scattering parameters (asymmetry, scattering/extinction cross-sections, albedo) and Mueller matrix elements (S11-S44)
|
|
10
|
+
- **PHIPS mode**: Custom detector DSCS values at 20 PHIPS detectors
|
|
11
|
+
- **Single geometry** or **ensemble averaging** (multiple geometries)
|
|
12
|
+
- **Parameter sweeps** for sensitivity studies
|
|
13
|
+
- **JSON serialization** for saving/loading results
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### Simple Example
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import goad_py as goad
|
|
21
|
+
|
|
22
|
+
# Run convergence on asymmetry parameter
|
|
23
|
+
results = goad.run_convergence(
|
|
24
|
+
geometry="hex.obj",
|
|
25
|
+
targets="asymmetry",
|
|
26
|
+
tolerance=0.01, # 1% relative tolerance
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
print(results.summary())
|
|
30
|
+
results.save("results.json")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Multiple Targets
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
results = goad.run_convergence(
|
|
37
|
+
geometry="hex.obj",
|
|
38
|
+
targets=["asymmetry", "scatt", "ext"],
|
|
39
|
+
tolerance=0.05, # 5% relative tolerance
|
|
40
|
+
batch_size=24,
|
|
41
|
+
)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Mueller Matrix Elements
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# Converge all theta bins
|
|
48
|
+
results = goad.run_convergence(
|
|
49
|
+
geometry="hex.obj",
|
|
50
|
+
targets="S11",
|
|
51
|
+
tolerance=0.25,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Or converge specific bins only (e.g., backscattering)
|
|
55
|
+
results = goad.run_convergence(
|
|
56
|
+
geometry="hex.obj",
|
|
57
|
+
targets=[{
|
|
58
|
+
"variable": "S11",
|
|
59
|
+
"tolerance": 0.10,
|
|
60
|
+
"theta_indices": [180] # Only backscattering
|
|
61
|
+
}],
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Ensemble Convergence
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# Pass a directory instead of a file
|
|
69
|
+
results = goad.run_convergence(
|
|
70
|
+
geometry="./geometries/", # Directory with .obj files
|
|
71
|
+
targets=["asymmetry", "scatt"],
|
|
72
|
+
tolerance=0.05,
|
|
73
|
+
)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### PHIPS Detector Convergence
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
results = goad.run_convergence(
|
|
80
|
+
geometry="hex.obj",
|
|
81
|
+
targets="phips_dscs",
|
|
82
|
+
tolerance=0.25,
|
|
83
|
+
mode="phips",
|
|
84
|
+
phips_bins_file="phips_bins_edges.toml",
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Advanced Usage
|
|
89
|
+
|
|
90
|
+
### Using ConvergenceConfig
|
|
91
|
+
|
|
92
|
+
For full control over all parameters:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from goad_py import ConvergenceConfig, StandardMode, UnifiedConvergence
|
|
96
|
+
|
|
97
|
+
config = ConvergenceConfig(
|
|
98
|
+
geometry="hex.obj",
|
|
99
|
+
mode=StandardMode(n_theta=181, n_phi=181),
|
|
100
|
+
convergence_targets=[
|
|
101
|
+
{"variable": "asymmetry", "tolerance": 0.005, "tolerance_type": "absolute"},
|
|
102
|
+
{"variable": "scatt", "tolerance": 0.01, "tolerance_type": "relative"},
|
|
103
|
+
],
|
|
104
|
+
wavelength=0.633, # Red laser
|
|
105
|
+
particle_refr_index_re=1.5,
|
|
106
|
+
particle_refr_index_im=0.01,
|
|
107
|
+
batch_size=24,
|
|
108
|
+
max_orientations=10_000,
|
|
109
|
+
min_batches=10,
|
|
110
|
+
mueller_1d=True,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
conv = UnifiedConvergence(config)
|
|
114
|
+
results = conv.run()
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Parameter Sweeps
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from goad_py import ConvergenceConfig, StandardMode, run_convergence_sweep
|
|
121
|
+
|
|
122
|
+
# Wavelength sweep
|
|
123
|
+
wavelengths = [0.532, 0.633, 0.780]
|
|
124
|
+
configs = [
|
|
125
|
+
ConvergenceConfig(
|
|
126
|
+
geometry="hex.obj",
|
|
127
|
+
mode=StandardMode(),
|
|
128
|
+
convergence_targets=[{"variable": "asymmetry", "tolerance": 0.05}],
|
|
129
|
+
wavelength=wl,
|
|
130
|
+
)
|
|
131
|
+
for wl in wavelengths
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
results_list = run_convergence_sweep(configs)
|
|
135
|
+
|
|
136
|
+
for wl, result in zip(wavelengths, results_list):
|
|
137
|
+
print(f"λ={wl}: asymmetry = {result.values['asymmetry']:.6f}")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## API Reference
|
|
141
|
+
|
|
142
|
+
### Functions
|
|
143
|
+
|
|
144
|
+
#### `run_convergence(geometry, targets, tolerance=0.05, tolerance_type="relative", mode="auto", **kwargs)`
|
|
145
|
+
|
|
146
|
+
Main entry point for convergence studies.
|
|
147
|
+
|
|
148
|
+
**Parameters:**
|
|
149
|
+
- `geometry` (str|Path): Path to .obj file or directory
|
|
150
|
+
- `targets` (str|list): What to converge on (e.g., "asymmetry", ["asymmetry", "scatt"], or list of dicts)
|
|
151
|
+
- `tolerance` (float): Default tolerance for convergence
|
|
152
|
+
- `tolerance_type` (str): "relative" or "absolute"
|
|
153
|
+
- `mode` (str|ConvergenceMode): "auto", "standard", "phips", or a ConvergenceMode instance
|
|
154
|
+
- `**kwargs`: Additional settings (wavelength, batch_size, etc.)
|
|
155
|
+
|
|
156
|
+
**Returns:** `UnifiedResults`
|
|
157
|
+
|
|
158
|
+
#### `run_convergence_sweep(configs, parallel=False)`
|
|
159
|
+
|
|
160
|
+
Run multiple convergence studies for parameter sweeps.
|
|
161
|
+
|
|
162
|
+
**Parameters:**
|
|
163
|
+
- `configs` (List[ConvergenceConfig]): List of configurations
|
|
164
|
+
- `parallel` (bool): Run in parallel (not yet implemented)
|
|
165
|
+
|
|
166
|
+
**Returns:** `List[UnifiedResults]`
|
|
167
|
+
|
|
168
|
+
### Classes
|
|
169
|
+
|
|
170
|
+
#### `StandardMode(n_theta=181, n_phi=181)`
|
|
171
|
+
|
|
172
|
+
Standard convergence mode for integrated parameters and Mueller elements.
|
|
173
|
+
|
|
174
|
+
**Valid targets:**
|
|
175
|
+
- Scalar: `asymmetry`, `scatt`, `ext`, `albedo`
|
|
176
|
+
- Mueller: `S11`, `S12`, ..., `S44`
|
|
177
|
+
- Mueller with specific bins: `{"variable": "S11", "theta_indices": [0, 180]}`
|
|
178
|
+
|
|
179
|
+
#### `PHIPSMode(bins_file)`
|
|
180
|
+
|
|
181
|
+
PHIPS detector convergence mode.
|
|
182
|
+
|
|
183
|
+
**Parameters:**
|
|
184
|
+
- `bins_file` (str): Path to PHIPS bins TOML file
|
|
185
|
+
|
|
186
|
+
**Valid targets:**
|
|
187
|
+
- Only supports a single target with tolerance specification
|
|
188
|
+
- Optionally specify `detector_indices` to converge specific detectors
|
|
189
|
+
|
|
190
|
+
#### `ConvergenceConfig`
|
|
191
|
+
|
|
192
|
+
Full configuration for convergence studies.
|
|
193
|
+
|
|
194
|
+
**Required fields:**
|
|
195
|
+
- `geometry` (str|Path): Geometry file or directory
|
|
196
|
+
- `mode` (ConvergenceMode): StandardMode or PHIPSMode
|
|
197
|
+
- `convergence_targets` (List[dict]): List of target specifications
|
|
198
|
+
|
|
199
|
+
**Optional fields:**
|
|
200
|
+
- `wavelength` (float): Default 0.532 μm
|
|
201
|
+
- `particle_refr_index_re/im` (float): Refractive index (default: 1.31+0j)
|
|
202
|
+
- `medium_refr_index_re/im` (float): Medium refractive index (default: 1.0+0j)
|
|
203
|
+
- `batch_size` (int): Orientations per batch (default: 24)
|
|
204
|
+
- `max_orientations` (int): Maximum orientations (default: 100,000)
|
|
205
|
+
- `min_batches` (int): Minimum batches before convergence (default: 10)
|
|
206
|
+
- `mueller_1d` (bool): Compute 1D Mueller matrix (default: True, standard mode only)
|
|
207
|
+
- `mueller_2d` (bool): Compute 2D Mueller matrix (default: False, standard mode only)
|
|
208
|
+
|
|
209
|
+
#### `UnifiedResults`
|
|
210
|
+
|
|
211
|
+
Results container with uniform interface.
|
|
212
|
+
|
|
213
|
+
**Attributes:**
|
|
214
|
+
- `converged` (bool): Whether convergence criteria were met
|
|
215
|
+
- `n_orientations` (int): Total orientations run
|
|
216
|
+
- `mode` (str): "standard" or "phips"
|
|
217
|
+
- `is_ensemble` (bool): Whether ensemble averaging was used
|
|
218
|
+
- `values` (dict): Final mean values
|
|
219
|
+
- `sem_values` (dict): Final SEM values
|
|
220
|
+
- `mueller_1d` (ndarray): 1D Mueller matrix (if computed)
|
|
221
|
+
- `mueller_2d` (ndarray): 2D Mueller matrix (if computed)
|
|
222
|
+
- `convergence_history` (list): Convergence progress
|
|
223
|
+
- `warning` (str): Warning message (if any)
|
|
224
|
+
|
|
225
|
+
**Methods:**
|
|
226
|
+
- `summary()`: Generate human-readable summary
|
|
227
|
+
- `save(path)`: Save to JSON file
|
|
228
|
+
- `load(path)`: Load from JSON file (class method)
|
|
229
|
+
- `to_dict()`: Convert to dictionary
|
|
230
|
+
|
|
231
|
+
## Validation
|
|
232
|
+
|
|
233
|
+
The API performs comprehensive input validation:
|
|
234
|
+
|
|
235
|
+
### Mode-Specific Validation
|
|
236
|
+
|
|
237
|
+
**StandardMode:**
|
|
238
|
+
- Rejects invalid variable names
|
|
239
|
+
- Rejects `theta_indices` on non-Mueller variables
|
|
240
|
+
- Validates theta_indices are within range
|
|
241
|
+
|
|
242
|
+
**PHIPSMode:**
|
|
243
|
+
- Requires valid PHIPS bins TOML file
|
|
244
|
+
- Only allows a single convergence target
|
|
245
|
+
- Rejects `mueller_1d` and `mueller_2d` options (not compatible with custom binning)
|
|
246
|
+
|
|
247
|
+
### Config Validation
|
|
248
|
+
|
|
249
|
+
- Geometry path must exist
|
|
250
|
+
- `mode` must be a ConvergenceMode instance
|
|
251
|
+
- `convergence_targets` cannot be empty
|
|
252
|
+
- Numeric parameters must be positive
|
|
253
|
+
- Mode-specific targets are validated
|
|
254
|
+
|
|
255
|
+
**The API throws errors immediately rather than failing silently.**
|
|
256
|
+
|
|
257
|
+
## Examples
|
|
258
|
+
|
|
259
|
+
See `unified_convergence_example.py` for comprehensive examples covering:
|
|
260
|
+
|
|
261
|
+
1. Simple convergence on single parameter
|
|
262
|
+
2. Multiple convergence targets
|
|
263
|
+
3. Mueller matrix element convergence
|
|
264
|
+
4. Backscatter convergence (specific theta bins)
|
|
265
|
+
5. Ensemble convergence
|
|
266
|
+
6. PHIPS detector convergence
|
|
267
|
+
7. PHIPS ensemble convergence
|
|
268
|
+
8. Advanced usage with ConvergenceConfig
|
|
269
|
+
9. Parameter sweeps
|
|
270
|
+
10. Save/load results
|
|
271
|
+
|
|
272
|
+
## Migration from Legacy API
|
|
273
|
+
|
|
274
|
+
The legacy convergence classes (`Convergence`, `EnsembleConvergence`, `PHIPSConvergence`, etc.) are still available, but the unified API is recommended for new code.
|
|
275
|
+
|
|
276
|
+
### Before (Legacy):
|
|
277
|
+
```python
|
|
278
|
+
from goad_py import Convergence, Convergable
|
|
279
|
+
|
|
280
|
+
convergables = [
|
|
281
|
+
Convergable('asymmetry', 'relative', 0.01),
|
|
282
|
+
Convergable('scatt', 'relative', 0.05),
|
|
283
|
+
]
|
|
284
|
+
conv = Convergence(settings, convergables, batch_size=24)
|
|
285
|
+
results = conv.run()
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### After (Unified):
|
|
289
|
+
```python
|
|
290
|
+
from goad_py import run_convergence
|
|
291
|
+
|
|
292
|
+
results = run_convergence(
|
|
293
|
+
geometry="hex.obj",
|
|
294
|
+
targets=[
|
|
295
|
+
{"variable": "asymmetry", "tolerance": 0.01},
|
|
296
|
+
{"variable": "scatt", "tolerance": 0.05},
|
|
297
|
+
],
|
|
298
|
+
batch_size=24,
|
|
299
|
+
)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Future Work
|
|
303
|
+
|
|
304
|
+
- Parallel execution for parameter sweeps
|
|
305
|
+
- Additional output formats (HDF5, NetCDF)
|
|
306
|
+
- Plotting utilities integrated into UnifiedResults
|
|
307
|
+
- Progress callbacks for long-running convergences
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Unified Convergence API - Implementation Summary
|
|
2
|
+
|
|
3
|
+
## What We Built
|
|
4
|
+
|
|
5
|
+
A unified, single-entry-point API for all GOAD convergence studies with:
|
|
6
|
+
|
|
7
|
+
✅ **Strict validation** - Errors are thrown immediately for invalid inputs
|
|
8
|
+
✅ **Mode-based architecture** - StandardMode and PHIPSMode with specific validation rules
|
|
9
|
+
✅ **Uniform output** - UnifiedResults with JSON serialization
|
|
10
|
+
✅ **Auto-detection** - Automatically selects appropriate mode based on targets
|
|
11
|
+
✅ **Single or ensemble** - Automatically handles file vs directory geometry input
|
|
12
|
+
✅ **Parameter sweeps** - Built-in support for running multiple configurations
|
|
13
|
+
✅ **Backward compatible** - Legacy API still available
|
|
14
|
+
|
|
15
|
+
## Files Created
|
|
16
|
+
|
|
17
|
+
1. **`python/goad_py/unified_convergence.py`** (~850 lines)
|
|
18
|
+
- `ConvergenceMode` abstract base class
|
|
19
|
+
- `StandardMode` - for integrated parameters and Mueller elements
|
|
20
|
+
- `PHIPSMode` - for PHIPS detector DSCS
|
|
21
|
+
- `ConvergenceConfig` - validated configuration dataclass
|
|
22
|
+
- `UnifiedResults` - results container with JSON save/load
|
|
23
|
+
- `UnifiedConvergence` - main convergence runner
|
|
24
|
+
- `run_convergence()` - primary entry point
|
|
25
|
+
- `run_convergence_sweep()` - parameter sweep support
|
|
26
|
+
|
|
27
|
+
2. **`python/goad_py/__init__.py`** - Updated exports
|
|
28
|
+
|
|
29
|
+
3. **`unified_convergence_example.py`** - Comprehensive examples
|
|
30
|
+
|
|
31
|
+
4. **`test_unified_validation.py`** - Validation tests (all passing ✓)
|
|
32
|
+
|
|
33
|
+
5. **`test_unified_quick.py`** - End-to-end tests (all passing ✓)
|
|
34
|
+
|
|
35
|
+
6. **`UNIFIED_API.md`** - Complete documentation
|
|
36
|
+
|
|
37
|
+
## Key Design Decisions
|
|
38
|
+
|
|
39
|
+
### 1. Mode Classes Enforce Validation
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
class StandardMode:
|
|
43
|
+
VALID_SCALAR_TARGETS = {"asymmetry", "scatt", "ext", "albedo"}
|
|
44
|
+
VALID_MUELLER_TARGETS = {"S11", "S12", ..., "S44"}
|
|
45
|
+
|
|
46
|
+
class PHIPSMode:
|
|
47
|
+
def __init__(self, bins_file: str):
|
|
48
|
+
# Validates bins file exists and is valid TOML
|
|
49
|
+
# Loads custom bins for later use
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Rationale:** Each mode knows what's valid for itself. StandardMode can't be used with PHIPS bins, and PHIPSMode can't compute integrated parameters.
|
|
53
|
+
|
|
54
|
+
### 2. ConvergenceConfig Validates in __post_init__
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
@dataclass
|
|
58
|
+
class ConvergenceConfig:
|
|
59
|
+
def __post_init__(self):
|
|
60
|
+
# Validate geometry exists
|
|
61
|
+
# Validate mode is correct type
|
|
62
|
+
# Let mode validate its targets
|
|
63
|
+
# Validate numeric parameters > 0
|
|
64
|
+
# Check mode-specific constraints
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Rationale:** Fail fast with clear error messages rather than mysterious failures during convergence.
|
|
68
|
+
|
|
69
|
+
### 3. Single Entry Point with Smart Defaults
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
run_convergence(
|
|
73
|
+
geometry="hex.obj",
|
|
74
|
+
targets="asymmetry", # Can be string, list of strings, or list of dicts
|
|
75
|
+
tolerance=0.05, # Applied to all unless overridden
|
|
76
|
+
mode="auto", # Auto-detect from targets
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Rationale:** Easy for simple cases, but can scale to complex configurations.
|
|
81
|
+
|
|
82
|
+
### 4. Uniform Results Format
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
@dataclass
|
|
86
|
+
class UnifiedResults:
|
|
87
|
+
converged: bool
|
|
88
|
+
n_orientations: int
|
|
89
|
+
mode: str
|
|
90
|
+
is_ensemble: bool
|
|
91
|
+
values: Dict[str, Union[float, np.ndarray]]
|
|
92
|
+
sem_values: Dict[str, Union[float, np.ndarray]]
|
|
93
|
+
# ... plus save/load/summary methods
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Rationale:** Same interface regardless of mode, making parameter sweeps trivial.
|
|
97
|
+
|
|
98
|
+
## Usage Examples
|
|
99
|
+
|
|
100
|
+
### Simplest Case
|
|
101
|
+
```python
|
|
102
|
+
results = goad.run_convergence("hex.obj", "asymmetry", tolerance=0.01)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Multiple Targets
|
|
106
|
+
```python
|
|
107
|
+
results = goad.run_convergence(
|
|
108
|
+
"hex.obj",
|
|
109
|
+
["asymmetry", "scatt"],
|
|
110
|
+
tolerance=0.05
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Mueller Elements with Specific Bins
|
|
115
|
+
```python
|
|
116
|
+
results = goad.run_convergence(
|
|
117
|
+
"hex.obj",
|
|
118
|
+
[{"variable": "S11", "tolerance": 0.1, "theta_indices": [180]}]
|
|
119
|
+
)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Ensemble
|
|
123
|
+
```python
|
|
124
|
+
results = goad.run_convergence("./geometries/", "asymmetry", tolerance=0.05)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### PHIPS
|
|
128
|
+
```python
|
|
129
|
+
results = goad.run_convergence(
|
|
130
|
+
"./geometries/",
|
|
131
|
+
"phips_dscs",
|
|
132
|
+
tolerance=0.25,
|
|
133
|
+
mode="phips",
|
|
134
|
+
phips_bins_file="phips_bins_edges.toml"
|
|
135
|
+
)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Parameter Sweep
|
|
139
|
+
```python
|
|
140
|
+
configs = [
|
|
141
|
+
ConvergenceConfig(
|
|
142
|
+
geometry="hex.obj",
|
|
143
|
+
mode=StandardMode(),
|
|
144
|
+
convergence_targets=[{"variable": "asymmetry", "tolerance": 0.05}],
|
|
145
|
+
wavelength=wl
|
|
146
|
+
)
|
|
147
|
+
for wl in [0.532, 0.633, 0.780]
|
|
148
|
+
]
|
|
149
|
+
results_list = run_convergence_sweep(configs)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Validation Features
|
|
153
|
+
|
|
154
|
+
### What Gets Validated
|
|
155
|
+
|
|
156
|
+
✓ Geometry path exists (file or directory)
|
|
157
|
+
✓ Mode is correct type (StandardMode or PHIPSMode)
|
|
158
|
+
✓ Targets are valid for the mode
|
|
159
|
+
✓ PHIPS bins file exists and is valid TOML
|
|
160
|
+
✓ Numeric parameters are positive
|
|
161
|
+
✓ Mueller options not used in PHIPS mode
|
|
162
|
+
✓ theta_indices only used with Mueller elements
|
|
163
|
+
✓ Empty convergence_targets rejected
|
|
164
|
+
|
|
165
|
+
### Error Examples
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# Invalid geometry
|
|
169
|
+
config = ConvergenceConfig(geometry="nonexistent.obj", ...)
|
|
170
|
+
# FileNotFoundError: Geometry path does not exist
|
|
171
|
+
|
|
172
|
+
# Invalid target for StandardMode
|
|
173
|
+
run_convergence("hex.obj", targets="invalid_target")
|
|
174
|
+
# ValueError: Invalid target 'invalid_target' for StandardMode
|
|
175
|
+
|
|
176
|
+
# Mueller options in PHIPS mode
|
|
177
|
+
ConvergenceConfig(mode=PHIPSMode(...), mueller_1d=True, ...)
|
|
178
|
+
# ValueError: mueller_1d not supported in PHIPSMode
|
|
179
|
+
|
|
180
|
+
# theta_indices on non-Mueller variable
|
|
181
|
+
run_convergence("hex.obj", [{
|
|
182
|
+
"variable": "asymmetry",
|
|
183
|
+
"theta_indices": [180]
|
|
184
|
+
}])
|
|
185
|
+
# ValueError: theta_indices can only be used with Mueller elements
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Testing
|
|
189
|
+
|
|
190
|
+
All tests passing ✓
|
|
191
|
+
|
|
192
|
+
**Validation tests** (`test_unified_validation.py`):
|
|
193
|
+
- StandardMode creation and validation
|
|
194
|
+
- PHIPSMode validation
|
|
195
|
+
- ConvergenceConfig validation
|
|
196
|
+
- Invalid input rejection
|
|
197
|
+
- Config serialization
|
|
198
|
+
|
|
199
|
+
**End-to-end tests** (`test_unified_quick.py`):
|
|
200
|
+
- Simple convergence
|
|
201
|
+
- Multiple targets
|
|
202
|
+
- Mueller element convergence
|
|
203
|
+
- Save/load JSON
|
|
204
|
+
- Using ConvergenceConfig directly
|
|
205
|
+
- Summary and serialization
|
|
206
|
+
|
|
207
|
+
## Future Enhancements
|
|
208
|
+
|
|
209
|
+
1. **Parallel parameter sweeps** - Use multiprocessing for sweep execution
|
|
210
|
+
2. **Plotting integration** - `results.plot()` method for standard visualizations
|
|
211
|
+
3. **HDF5/NetCDF output** - For large datasets and Mueller matrices
|
|
212
|
+
4. **Progress callbacks** - For monitoring long convergences
|
|
213
|
+
5. **Convergence diagnostics** - Autocorrelation, effective sample size, etc.
|
|
214
|
+
|
|
215
|
+
## Migration Path
|
|
216
|
+
|
|
217
|
+
The legacy API (`Convergence`, `EnsembleConvergence`, `PHIPSConvergence`) remains available and functional. Users can migrate incrementally:
|
|
218
|
+
|
|
219
|
+
1. **Phase 1**: Use unified API for new scripts
|
|
220
|
+
2. **Phase 2**: Update documentation to recommend unified API
|
|
221
|
+
3. **Phase 3**: Add deprecation warnings to legacy API
|
|
222
|
+
4. **Phase 4**: Remove legacy API (major version bump)
|
|
223
|
+
|
|
224
|
+
No breaking changes in this release.
|