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.

Files changed (139) hide show
  1. {goad_py-0.5.1 → goad_py-0.5.5}/PKG-INFO +3 -1
  2. {goad_py-0.5.1 → goad_py-0.5.5}/config/default.toml +7 -15
  3. goad_py-0.5.5/goad-py/UNIFIED_API.md +307 -0
  4. goad_py-0.5.5/goad-py/UNIFIED_API_SUMMARY.md +224 -0
  5. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/convergence.py +244 -46
  6. goad_py-0.5.5/goad-py/examples/README.md +154 -0
  7. goad_py-0.5.5/goad-py/examples/direct/README.md +71 -0
  8. goad_py-0.5.5/goad-py/examples/direct/backscatter_convergence_example.py +140 -0
  9. goad_py-0.5.5/goad-py/examples/direct/ensemble_example.py +73 -0
  10. {goad_py-0.5.1/goad-py → goad_py-0.5.5/goad-py/examples/direct}/multiproblem_example.py +1 -1
  11. goad_py-0.5.5/goad-py/examples/direct/phips_convergence_example.py +211 -0
  12. goad_py-0.5.5/goad-py/examples/direct/phips_ensemble_convergence_example.py +231 -0
  13. goad_py-0.5.5/goad-py/examples/direct/s11_convergence_example.py +117 -0
  14. {goad_py-0.5.1/goad-py → goad_py-0.5.5/goad-py/examples/direct}/simple_example.py +1 -1
  15. goad_py-0.5.5/goad-py/examples/unified/01_simple_asymmetry.py +36 -0
  16. goad_py-0.5.5/goad-py/examples/unified/02_interval_binning.py +47 -0
  17. goad_py-0.5.5/goad-py/examples/unified/03_multiple_targets.py +35 -0
  18. goad_py-0.5.5/goad-py/examples/unified/04_mueller_element.py +37 -0
  19. goad_py-0.5.5/goad-py/examples/unified/05_backscatter_specific_bin.py +41 -0
  20. goad_py-0.5.5/goad-py/examples/unified/06_ensemble_convergence.py +36 -0
  21. goad_py-0.5.5/goad-py/examples/unified/07_advanced_config.py +55 -0
  22. goad_py-0.5.5/goad-py/examples/unified/08_parameter_sweep.py +57 -0
  23. goad_py-0.5.5/goad-py/examples/unified/09_phips_convergence.py +98 -0
  24. goad_py-0.5.5/goad-py/examples/unified/09_phips_convergence_results.json +220 -0
  25. goad_py-0.5.5/goad-py/examples/unified/README.md +85 -0
  26. goad_py-0.5.5/goad-py/python/goad_py/__init__.py +51 -0
  27. goad_py-0.5.5/goad-py/python/goad_py/convergence.py +882 -0
  28. goad_py-0.5.5/goad-py/python/goad_py/phips_convergence.py +580 -0
  29. goad_py-0.5.5/goad-py/python/goad_py/unified_convergence.py +1085 -0
  30. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/release.sh +19 -19
  31. {goad_py-0.5.1 → goad_py-0.5.5}/pyproject.toml +5 -2
  32. goad_py-0.5.5/python/goad_py/__init__.py +51 -0
  33. goad_py-0.5.5/python/goad_py/convergence.py +882 -0
  34. goad_py-0.5.5/python/goad_py/phips_convergence.py +580 -0
  35. goad_py-0.5.5/python/goad_py/unified_convergence.py +1085 -0
  36. {goad_py-0.5.1 → goad_py-0.5.5}/src/bins.rs +48 -38
  37. {goad_py-0.5.1 → goad_py-0.5.5}/src/diff.rs +1 -3
  38. {goad_py-0.5.1 → goad_py-0.5.5}/src/result.rs +3 -1
  39. {goad_py-0.5.1 → goad_py-0.5.5}/src/settings.rs +144 -0
  40. goad_py-0.5.1/goad-py/python/goad_py/__init__.py +0 -7
  41. goad_py-0.5.1/goad-py/python/goad_py/convergence.py +0 -382
  42. goad_py-0.5.1/python/goad_py/__init__.py +0 -7
  43. goad_py-0.5.1/python/goad_py/convergence.py +0 -382
  44. {goad_py-0.5.1 → goad_py-0.5.5}/.github/workflows/python.yml +0 -0
  45. {goad_py-0.5.1 → goad_py-0.5.5}/.github/workflows/rust.yml +0 -0
  46. {goad_py-0.5.1 → goad_py-0.5.5}/.gitignore +0 -0
  47. {goad_py-0.5.1 → goad_py-0.5.5}/Cargo.lock +0 -0
  48. {goad_py-0.5.1 → goad_py-0.5.5}/Cargo.toml +0 -0
  49. {goad_py-0.5.1 → goad_py-0.5.5}/LICENSE +0 -0
  50. {goad_py-0.5.1 → goad_py-0.5.5}/README-python.md +0 -0
  51. {goad_py-0.5.1 → goad_py-0.5.5}/README.md +0 -0
  52. {goad_py-0.5.1 → goad_py-0.5.5}/blender/addon/__init__.py +0 -0
  53. {goad_py-0.5.1 → goad_py-0.5.5}/blender/addon/goad_py/__init__.py +0 -0
  54. {goad_py-0.5.1 → goad_py-0.5.5}/blender/addon.zip +0 -0
  55. {goad_py-0.5.1 → goad_py-0.5.5}/blender/build.sh +0 -0
  56. {goad_py-0.5.1 → goad_py-0.5.5}/blender/dev.blend +0 -0
  57. {goad_py-0.5.1 → goad_py-0.5.5}/config_editor.sh +0 -0
  58. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/8col.obj +0 -0
  59. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/clip_test.obj +0 -0
  60. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/concave1.obj +0 -0
  61. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/concave2.obj +0 -0
  62. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cone.obj +0 -0
  63. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cube.obj +0 -0
  64. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cube2.obj +0 -0
  65. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cube_inside_cube.obj +0 -0
  66. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cube_inside_hex.obj +0 -0
  67. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cube_inside_ico.obj +0 -0
  68. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/cubes.obj +0 -0
  69. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex.obj +0 -0
  70. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex2.obj +0 -0
  71. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex3.obj +0 -0
  72. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex4.obj +0 -0
  73. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex5.obj +0 -0
  74. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex6.obj +0 -0
  75. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex7.obj +0 -0
  76. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex_20_30_30.obj +0 -0
  77. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex_distort.obj +0 -0
  78. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex_hollow.obj +0 -0
  79. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/hex_indented.obj +0 -0
  80. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/icosphere1.obj +0 -0
  81. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/multiple.obj +0 -0
  82. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/multiple2.obj +0 -0
  83. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/multiple3.obj +0 -0
  84. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/octo.obj +0 -0
  85. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/octo2.obj +0 -0
  86. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/para.obj +0 -0
  87. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/para_rough1.obj +0 -0
  88. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/plane_xy.obj +0 -0
  89. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/plane_yz.obj +0 -0
  90. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/plate.obj +0 -0
  91. {goad_py-0.5.1 → goad_py-0.5.5}/examples/data/plate_distort.obj +0 -0
  92. {goad_py-0.5.1 → goad_py-0.5.5}/examples/multi-problem.rs +0 -0
  93. {goad_py-0.5.1 → goad_py-0.5.5}/examples/problem-diff.rs +0 -0
  94. {goad_py-0.5.1 → goad_py-0.5.5}/examples/problem1.rs +0 -0
  95. {goad_py-0.5.1 → goad_py-0.5.5}/examples/simplify.rs +0 -0
  96. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/.gitignore +0 -0
  97. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/CLAUDE.md +0 -0
  98. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/Cargo.toml +0 -0
  99. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/DISTRIBUTION.md +0 -0
  100. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/README-python.md +0 -0
  101. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/build_and_test.sh +0 -0
  102. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/build_wheels_local.sh +0 -0
  103. {goad_py-0.5.1/goad-py → goad_py-0.5.5/goad-py/examples/direct}/convergence_example.py +0 -0
  104. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/goad_py.pyi +0 -0
  105. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/plot.ipynb +0 -0
  106. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/publish_test.sh +0 -0
  107. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/python/goad_py/goad_py.pyi +0 -0
  108. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/src/lib.rs +0 -0
  109. {goad_py-0.5.1 → goad_py-0.5.5}/goad-py/test_wheels.sh +0 -0
  110. {goad_py-0.5.1 → goad_py-0.5.5}/python/goad_py/goad_py.pyi +0 -0
  111. {goad_py-0.5.1 → goad_py-0.5.5}/setup.sh +0 -0
  112. {goad_py-0.5.1 → goad_py-0.5.5}/src/_quickstart.rs +0 -0
  113. {goad_py-0.5.1 → goad_py-0.5.5}/src/beam.rs +0 -0
  114. {goad_py-0.5.1 → goad_py-0.5.5}/src/clip.rs +0 -0
  115. {goad_py-0.5.1 → goad_py-0.5.5}/src/containment.rs +0 -0
  116. {goad_py-0.5.1 → goad_py-0.5.5}/src/distortion.rs +0 -0
  117. {goad_py-0.5.1 → goad_py-0.5.5}/src/field.rs +0 -0
  118. {goad_py-0.5.1 → goad_py-0.5.5}/src/fresnel.rs +0 -0
  119. {goad_py-0.5.1 → goad_py-0.5.5}/src/geom.rs +0 -0
  120. {goad_py-0.5.1 → goad_py-0.5.5}/src/lib.rs +0 -0
  121. {goad_py-0.5.1 → goad_py-0.5.5}/src/main.rs +0 -0
  122. {goad_py-0.5.1 → goad_py-0.5.5}/src/multiproblem.rs +0 -0
  123. {goad_py-0.5.1 → goad_py-0.5.5}/src/orientation.rs +0 -0
  124. {goad_py-0.5.1 → goad_py-0.5.5}/src/output.rs +0 -0
  125. {goad_py-0.5.1 → goad_py-0.5.5}/src/params.rs +0 -0
  126. {goad_py-0.5.1 → goad_py-0.5.5}/src/powers.rs +0 -0
  127. {goad_py-0.5.1 → goad_py-0.5.5}/src/problem.rs +0 -0
  128. {goad_py-0.5.1 → goad_py-0.5.5}/src/settings/cli.rs +0 -0
  129. {goad_py-0.5.1 → goad_py-0.5.5}/src/settings/constants.rs +0 -0
  130. {goad_py-0.5.1 → goad_py-0.5.5}/src/settings/loading.rs +0 -0
  131. {goad_py-0.5.1 → goad_py-0.5.5}/src/settings/validation.rs +0 -0
  132. {goad_py-0.5.1 → goad_py-0.5.5}/src/snell.rs +0 -0
  133. {goad_py-0.5.1 → goad_py-0.5.5}/template/custom_bins.toml +0 -0
  134. {goad_py-0.5.1 → goad_py-0.5.5}/template/goad_pbs.sh +0 -0
  135. {goad_py-0.5.1 → goad_py-0.5.5}/tests/fixed_orientation_tests.rs +0 -0
  136. {goad_py-0.5.1 → goad_py-0.5.5}/tests/helpers.rs +0 -0
  137. {goad_py-0.5.1 → goad_py-0.5.5}/tests/test_data/fixed_hex_30_20_20_mueller_scatgrid +0 -0
  138. {goad_py-0.5.1 → goad_py-0.5.5}/tests/test_data/fixed_hex_30_30_30_mueller_scatgrid +0 -0
  139. {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.1
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 [theta,phi] angular bins
53
+ # [binning.scheme.Custom] # Custom binning with specified bin edges [[theta_min, theta_max], [phi_min, phi_max]]
54
54
  # bins = [
55
- # [
56
- # 0,
57
- # 0,
58
- # ],
59
- # [
60
- # 0,
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.