goad-py 0.5.6__tar.gz → 0.6.1__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 (137) hide show
  1. {goad_py-0.5.6 → goad_py-0.6.1}/PKG-INFO +1 -1
  2. goad_py-0.6.1/goad-py/examples/unified/10_advanced_parameters.py +216 -0
  3. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/python/goad_py/unified_convergence.py +245 -3
  4. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/src/lib.rs +4 -0
  5. {goad_py-0.5.6 → goad_py-0.6.1}/pyproject.toml +1 -1
  6. {goad_py-0.5.6 → goad_py-0.6.1}/python/goad_py/unified_convergence.py +245 -3
  7. {goad_py-0.5.6 → goad_py-0.6.1}/src/settings.rs +50 -0
  8. goad_py-0.5.6/goad-py/examples/unified/09_phips_convergence_results.json +0 -220
  9. {goad_py-0.5.6 → goad_py-0.6.1}/.github/workflows/python.yml +0 -0
  10. {goad_py-0.5.6 → goad_py-0.6.1}/.github/workflows/rust.yml +0 -0
  11. {goad_py-0.5.6 → goad_py-0.6.1}/.gitignore +0 -0
  12. {goad_py-0.5.6 → goad_py-0.6.1}/Cargo.lock +0 -0
  13. {goad_py-0.5.6 → goad_py-0.6.1}/Cargo.toml +0 -0
  14. {goad_py-0.5.6 → goad_py-0.6.1}/LICENSE +0 -0
  15. {goad_py-0.5.6 → goad_py-0.6.1}/README-python.md +0 -0
  16. {goad_py-0.5.6 → goad_py-0.6.1}/README.md +0 -0
  17. {goad_py-0.5.6 → goad_py-0.6.1}/blender/addon/__init__.py +0 -0
  18. {goad_py-0.5.6 → goad_py-0.6.1}/blender/addon/goad_py/__init__.py +0 -0
  19. {goad_py-0.5.6 → goad_py-0.6.1}/blender/addon.zip +0 -0
  20. {goad_py-0.5.6 → goad_py-0.6.1}/blender/build.sh +0 -0
  21. {goad_py-0.5.6 → goad_py-0.6.1}/blender/dev.blend +0 -0
  22. {goad_py-0.5.6 → goad_py-0.6.1}/config/default.toml +0 -0
  23. {goad_py-0.5.6 → goad_py-0.6.1}/config_editor.sh +0 -0
  24. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/8col.obj +0 -0
  25. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/clip_test.obj +0 -0
  26. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/concave1.obj +0 -0
  27. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/concave2.obj +0 -0
  28. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/cone.obj +0 -0
  29. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/cube.obj +0 -0
  30. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/cube2.obj +0 -0
  31. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/cube_inside_cube.obj +0 -0
  32. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/cube_inside_hex.obj +0 -0
  33. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/cube_inside_ico.obj +0 -0
  34. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/cubes.obj +0 -0
  35. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/hex.obj +0 -0
  36. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/hex2.obj +0 -0
  37. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/hex3.obj +0 -0
  38. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/hex4.obj +0 -0
  39. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/hex5.obj +0 -0
  40. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/hex6.obj +0 -0
  41. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/hex7.obj +0 -0
  42. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/hex_20_30_30.obj +0 -0
  43. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/hex_distort.obj +0 -0
  44. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/hex_hollow.obj +0 -0
  45. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/hex_indented.obj +0 -0
  46. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/icosphere1.obj +0 -0
  47. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/multiple.obj +0 -0
  48. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/multiple2.obj +0 -0
  49. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/multiple3.obj +0 -0
  50. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/octo.obj +0 -0
  51. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/octo2.obj +0 -0
  52. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/para.obj +0 -0
  53. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/para_rough1.obj +0 -0
  54. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/plane_xy.obj +0 -0
  55. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/plane_yz.obj +0 -0
  56. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/plate.obj +0 -0
  57. {goad_py-0.5.6 → goad_py-0.6.1}/examples/data/plate_distort.obj +0 -0
  58. {goad_py-0.5.6 → goad_py-0.6.1}/examples/multi-problem.rs +0 -0
  59. {goad_py-0.5.6 → goad_py-0.6.1}/examples/problem-diff.rs +0 -0
  60. {goad_py-0.5.6 → goad_py-0.6.1}/examples/problem1.rs +0 -0
  61. {goad_py-0.5.6 → goad_py-0.6.1}/examples/simplify.rs +0 -0
  62. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/.gitignore +0 -0
  63. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/CLAUDE.md +0 -0
  64. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/Cargo.toml +0 -0
  65. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/DISTRIBUTION.md +0 -0
  66. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/README-python.md +0 -0
  67. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/UNIFIED_API.md +0 -0
  68. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/UNIFIED_API_SUMMARY.md +0 -0
  69. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/build_and_test.sh +0 -0
  70. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/build_wheels_local.sh +0 -0
  71. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/convergence.py +0 -0
  72. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/README.md +0 -0
  73. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/direct/README.md +0 -0
  74. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/direct/backscatter_convergence_example.py +0 -0
  75. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/direct/convergence_example.py +0 -0
  76. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/direct/ensemble_example.py +0 -0
  77. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/direct/multiproblem_example.py +0 -0
  78. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/direct/multiproblem_phips_example.py +0 -0
  79. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/direct/phips_convergence_example.py +0 -0
  80. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/direct/phips_ensemble_convergence_example.py +0 -0
  81. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/direct/s11_convergence_example.py +0 -0
  82. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/direct/simple_example.py +0 -0
  83. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/unified/01_simple_asymmetry.py +0 -0
  84. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/unified/02_interval_binning.py +0 -0
  85. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/unified/03_multiple_targets.py +0 -0
  86. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/unified/04_mueller_element.py +0 -0
  87. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/unified/05_backscatter_specific_bin.py +0 -0
  88. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/unified/06_ensemble_convergence.py +0 -0
  89. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/unified/07_advanced_config.py +0 -0
  90. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/unified/08_parameter_sweep.py +0 -0
  91. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/unified/09_phips_convergence.py +0 -0
  92. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/examples/unified/README.md +0 -0
  93. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/goad_py.pyi +0 -0
  94. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/plot.ipynb +0 -0
  95. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/publish_test.sh +0 -0
  96. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/python/goad_py/__init__.py +0 -0
  97. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/python/goad_py/convergence.py +0 -0
  98. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/python/goad_py/goad_py.pyi +0 -0
  99. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/python/goad_py/phips_convergence.py +0 -0
  100. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/release.sh +0 -0
  101. {goad_py-0.5.6 → goad_py-0.6.1}/goad-py/test_wheels.sh +0 -0
  102. {goad_py-0.5.6 → goad_py-0.6.1}/python/goad_py/__init__.py +0 -0
  103. {goad_py-0.5.6 → goad_py-0.6.1}/python/goad_py/convergence.py +0 -0
  104. {goad_py-0.5.6 → goad_py-0.6.1}/python/goad_py/goad_py.pyi +0 -0
  105. {goad_py-0.5.6 → goad_py-0.6.1}/python/goad_py/phips_convergence.py +0 -0
  106. {goad_py-0.5.6 → goad_py-0.6.1}/setup.sh +0 -0
  107. {goad_py-0.5.6 → goad_py-0.6.1}/src/_quickstart.rs +0 -0
  108. {goad_py-0.5.6 → goad_py-0.6.1}/src/beam.rs +0 -0
  109. {goad_py-0.5.6 → goad_py-0.6.1}/src/bins.rs +0 -0
  110. {goad_py-0.5.6 → goad_py-0.6.1}/src/clip.rs +0 -0
  111. {goad_py-0.5.6 → goad_py-0.6.1}/src/containment.rs +0 -0
  112. {goad_py-0.5.6 → goad_py-0.6.1}/src/diff.rs +0 -0
  113. {goad_py-0.5.6 → goad_py-0.6.1}/src/distortion.rs +0 -0
  114. {goad_py-0.5.6 → goad_py-0.6.1}/src/field.rs +0 -0
  115. {goad_py-0.5.6 → goad_py-0.6.1}/src/fresnel.rs +0 -0
  116. {goad_py-0.5.6 → goad_py-0.6.1}/src/geom.rs +0 -0
  117. {goad_py-0.5.6 → goad_py-0.6.1}/src/lib.rs +0 -0
  118. {goad_py-0.5.6 → goad_py-0.6.1}/src/main.rs +0 -0
  119. {goad_py-0.5.6 → goad_py-0.6.1}/src/multiproblem.rs +0 -0
  120. {goad_py-0.5.6 → goad_py-0.6.1}/src/orientation.rs +0 -0
  121. {goad_py-0.5.6 → goad_py-0.6.1}/src/output.rs +0 -0
  122. {goad_py-0.5.6 → goad_py-0.6.1}/src/params.rs +0 -0
  123. {goad_py-0.5.6 → goad_py-0.6.1}/src/powers.rs +0 -0
  124. {goad_py-0.5.6 → goad_py-0.6.1}/src/problem.rs +0 -0
  125. {goad_py-0.5.6 → goad_py-0.6.1}/src/result.rs +0 -0
  126. {goad_py-0.5.6 → goad_py-0.6.1}/src/settings/cli.rs +0 -0
  127. {goad_py-0.5.6 → goad_py-0.6.1}/src/settings/constants.rs +0 -0
  128. {goad_py-0.5.6 → goad_py-0.6.1}/src/settings/loading.rs +0 -0
  129. {goad_py-0.5.6 → goad_py-0.6.1}/src/settings/validation.rs +0 -0
  130. {goad_py-0.5.6 → goad_py-0.6.1}/src/snell.rs +0 -0
  131. {goad_py-0.5.6 → goad_py-0.6.1}/template/custom_bins.toml +0 -0
  132. {goad_py-0.5.6 → goad_py-0.6.1}/template/goad_pbs.sh +0 -0
  133. {goad_py-0.5.6 → goad_py-0.6.1}/tests/fixed_orientation_tests.rs +0 -0
  134. {goad_py-0.5.6 → goad_py-0.6.1}/tests/helpers.rs +0 -0
  135. {goad_py-0.5.6 → goad_py-0.6.1}/tests/test_data/fixed_hex_30_20_20_mueller_scatgrid +0 -0
  136. {goad_py-0.5.6 → goad_py-0.6.1}/tests/test_data/fixed_hex_30_30_30_mueller_scatgrid +0 -0
  137. {goad_py-0.5.6 → goad_py-0.6.1}/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.6
3
+ Version: 0.6.1
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Science/Research
6
6
  Classifier: Topic :: Scientific/Engineering :: Physics
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Example 10: Advanced Parameters - Comprehensive Demonstration
4
+
5
+ Demonstrates ALL modifiable parameters in run_convergence():
6
+ - Optical parameters (wavelength, refractive indices)
7
+ - Convergence parameters (batch size, max orientations, min batches)
8
+ - Beam tracing parameters (thresholds, recursion limits, cutoff)
9
+ - Geometry transformations (problem scale, per-axis scaling, distortion)
10
+ - Advanced configuration (mapping, coherence, field of view)
11
+ - Reproducibility (random seed)
12
+
13
+ This example showcases the full flexibility of the unified convergence API.
14
+ """
15
+
16
+ from pathlib import Path
17
+ import goad_py as goad
18
+
19
+ print("=" * 80)
20
+ print("Example 10: Advanced Parameters - ALL Modifiable Parameters")
21
+ print("=" * 80)
22
+
23
+ # Get paths relative to this script's location
24
+ script_dir = Path(__file__).parent
25
+ geom_file = script_dir / "../../../examples/data/hex.obj"
26
+ phips_bins_file = script_dir / "../../../phips_bins_edges.toml"
27
+
28
+ # Resolve to absolute paths
29
+ geom_file = str(geom_file.resolve())
30
+ phips_bins_file = str(phips_bins_file.resolve())
31
+
32
+ # Configuration summary
33
+ print("\n" + "=" * 80)
34
+ print("Configuration Summary:")
35
+ print("=" * 80)
36
+ print("OPTICAL PARAMETERS:")
37
+ print(" Wavelength: 0.633 μm (HeNe laser)")
38
+ print(" Particle refractive index: 1.5 + 0.01i (absorbing particle)")
39
+ print(" Medium refractive index: 1.33 + 0.0i (water)")
40
+ print()
41
+ print("CONVERGENCE PARAMETERS:")
42
+ print(" Batch size: 12 orientations per batch")
43
+ print(" Max orientations: 500")
44
+ print(" Min batches: 3")
45
+ print()
46
+ print("BEAM TRACING PARAMETERS:")
47
+ print(" Beam power threshold: 0.01 (stricter than default 0.05)")
48
+ print(" Beam area threshold factor: 2.0 (half of default 4.0)")
49
+ print(" Cutoff: 0.0005 (stricter than default 0.001)")
50
+ print(" Max recursion depth: 6 (limited for demonstration)")
51
+ print(" Max TIR bounces: 50 (half of default 100)")
52
+ print()
53
+ print("GEOMETRY TRANSFORMATIONS:")
54
+ print(" Problem scale: 2.0 (scales entire problem by 2x)")
55
+ print(" Per-axis geometry scale: [1.0, 1.0, 1.5] (stretch z-axis by 1.5x)")
56
+ print(" Distortion: 0.1 (adds 10% geometric distortion)")
57
+ print()
58
+ print("ADVANCED CONFIGURATION:")
59
+ print(" Mapping: GeometricOptics (instead of default ApertureDiffraction)")
60
+ print(" Coherence: True (enable coherent scattering calculations)")
61
+ print(" Field of view factor: 2.0 (doubled FOV)")
62
+ print()
63
+ print("REPRODUCIBILITY:")
64
+ print(" Random seed: 42 (for reproducible results)")
65
+ print()
66
+ print("CONVERGENCE TARGET:")
67
+ print(" Mode: PHIPS detector convergence")
68
+ print(" Detectors: Indices 1-19 (26° to 170°, skipping 18° forward scatter)")
69
+ print(" Tolerance: 50% relative SEM (relaxed for demo)")
70
+ print("=" * 80 + "\n")
71
+
72
+ # Converge on all PHIPS detectors except the first one (indices 1-19)
73
+ detector_indices = list(range(1, 20)) # [1, 2, 3, ..., 19]
74
+
75
+ print(f"Starting convergence on {len(detector_indices)} PHIPS detectors...\n")
76
+
77
+ # Run PHIPS convergence with ALL MODIFIABLE PARAMETERS
78
+ results = goad.run_convergence(
79
+ # Required parameters
80
+ geometry=geom_file,
81
+ targets=[
82
+ {
83
+ "tolerance": 0.25, # 50% relative SEM (relaxed for demo)
84
+ "tolerance_type": "relative",
85
+ "detector_indices": detector_indices,
86
+ }
87
+ ],
88
+ mode=goad.PHIPSMode(bins_file=phips_bins_file),
89
+ # ========================================================================
90
+ # OPTICAL PARAMETERS
91
+ # ========================================================================
92
+ wavelength=0.633, # HeNe laser wavelength in microns
93
+ particle_refr_index_re=1.5, # Real part of particle refractive index
94
+ particle_refr_index_im=0.01, # Imaginary part (absorption)
95
+ medium_refr_index_re=1.33, # Real part of medium refractive index (water)
96
+ medium_refr_index_im=0.0, # Imaginary part (no absorption in medium)
97
+ # ========================================================================
98
+ # CONVERGENCE PARAMETERS
99
+ # ========================================================================
100
+ batch_size=12, # Orientations per batch (smaller batches for finer control)
101
+ max_orientations=500, # Maximum total orientations
102
+ min_batches=10, # Minimum batches before checking convergence
103
+ mueller_1d=False, # Mueller matrix output (N/A for PHIPS mode)
104
+ # ========================================================================
105
+ # BEAM TRACING PARAMETERS
106
+ # ========================================================================
107
+ beam_power_threshold=0.01, # Beam power threshold (stricter than default)
108
+ beam_area_threshold_fac=0.001, # Beam area threshold factor (tighter threshold)
109
+ cutoff=0.0005, # Ray power cutoff (more accurate but slower)
110
+ max_rec=6, # Maximum recursion depth (limited for demo)
111
+ max_tir=20, # Maximum total internal reflection bounces
112
+ # ========================================================================
113
+ # GEOMETRY TRANSFORMATIONS
114
+ # ========================================================================
115
+ scale=1.0, # Problem scaling factor (numerical changes only)
116
+ geom_scale=[3.0, 3.0, 6.0], # Per-axis geometry scaling
117
+ distortion=0.0, # Geometry distortion factor
118
+ # ========================================================================
119
+ # ADVANCED CONFIGURATION
120
+ # ========================================================================
121
+ mapping=goad.Mapping.ApertureDiffraction, # or GeometricOptics
122
+ coherence=True, # Enable coherent scattering calculations
123
+ fov_factor=2.0, # Field of view factor (doubled)
124
+ )
125
+
126
+ # Print summary
127
+ print(results.summary())
128
+
129
+ # Save results
130
+ results.save("10_advanced_parameters_results.json")
131
+
132
+ # Show detailed PHIPS output
133
+ print("\n" + "=" * 80)
134
+ print("PHIPS Detector DSCS Values (all 20 detectors)")
135
+ print("=" * 80)
136
+
137
+ import json
138
+
139
+ with open("10_advanced_parameters_results.json", "r") as f:
140
+ data = json.load(f)
141
+
142
+ phips_dscs = data.get("phips_dscs")
143
+ detector_angles = data.get("detector_angles")
144
+
145
+ if phips_dscs and detector_angles:
146
+ print(
147
+ f"\n{'Detector':<10} {'Angle':<10} {'DSCS Mean':<15} {'DSCS SEM':<15} {'Rel. SEM':<10}"
148
+ )
149
+ print("-" * 70)
150
+
151
+ for i, (dscs_data, angle) in enumerate(zip(phips_dscs, detector_angles)):
152
+ mean, sem = dscs_data[0], dscs_data[1]
153
+ rel_sem = (sem / abs(mean) * 100) if mean != 0 else float("inf")
154
+
155
+ # Mark which detectors were converged on
156
+ marker = "✓" if i in detector_indices else "○"
157
+
158
+ print(
159
+ f"{marker} {i:<8} {angle:<10.1f} {mean:<15.4e} {sem:<15.4e} {rel_sem:<10.2f}%"
160
+ )
161
+
162
+ print("\n" + "=" * 80)
163
+ print("Legend:")
164
+ print(" ✓ = Converged on this detector")
165
+ print(" ○ = Not a convergence target (but still computed)")
166
+ print("=" * 80)
167
+
168
+ # Show configuration that was used
169
+ print("\n" + "=" * 80)
170
+ print("Configuration Used (from saved results):")
171
+ print("=" * 80)
172
+
173
+ config = data.get("config", {})
174
+ if config:
175
+ print(f"\nOptical parameters:")
176
+ print(f" Wavelength: {config.get('wavelength')} μm")
177
+ print(
178
+ f" Particle refractive index: {config.get('particle_refr_index_re')} + {config.get('particle_refr_index_im')}i"
179
+ )
180
+ print(
181
+ f" Medium refractive index: {config.get('medium_refr_index_re')} + {config.get('medium_refr_index_im')}i"
182
+ )
183
+
184
+ beam_tracing = config.get("beam_tracing", {})
185
+ if beam_tracing:
186
+ print(f"\nBeam tracing parameters:")
187
+ print(f" Max recursion depth: {beam_tracing.get('max_rec')}")
188
+ print(f" Max TIR bounces: {beam_tracing.get('max_tir')}")
189
+ print(f" Cutoff: {beam_tracing.get('cutoff')}")
190
+ print(f" Beam power threshold: {beam_tracing.get('beam_power_threshold')}")
191
+ print(
192
+ f" Beam area threshold factor: {beam_tracing.get('beam_area_threshold_fac')}"
193
+ )
194
+
195
+ geom_transform = config.get("geometry_transform", {})
196
+ if geom_transform:
197
+ print(f"\nGeometry transformations:")
198
+ print(f" Problem scale: {geom_transform.get('scale')}")
199
+ if geom_transform.get("distortion"):
200
+ print(f" Distortion: {geom_transform.get('distortion')}")
201
+ if geom_transform.get("geom_scale"):
202
+ print(f" Per-axis geometry scale: {geom_transform.get('geom_scale')}")
203
+
204
+ advanced = config.get("advanced_config", {})
205
+ if advanced:
206
+ print(f"\nAdvanced configuration:")
207
+ print(f" Mapping: {advanced.get('mapping')}")
208
+ print(f" Coherence: {advanced.get('coherence')}")
209
+ if advanced.get("fov_factor"):
210
+ print(f" Field of view factor: {advanced.get('fov_factor')}")
211
+
212
+ if config.get("seed"):
213
+ print(f"\nReproducibility:")
214
+ print(f" Random seed: {config.get('seed')}")
215
+
216
+ print("\n✓ Example completed successfully!")
@@ -11,6 +11,8 @@ Features:
11
11
  - Strict input validation
12
12
  - Uniform output format (UnifiedResults)
13
13
  - Support for parameter sweeps
14
+ - Full control over beam tracing, geometry transformations, and advanced optics
15
+ - Reproducible results via random seed control
14
16
  """
15
17
 
16
18
  from dataclasses import dataclass, field, asdict
@@ -211,6 +213,96 @@ class PHIPSMode(ConvergenceMode):
211
213
  # ============================================================================
212
214
 
213
215
 
216
+ @dataclass
217
+ class BeamTracingConfig:
218
+ """Beam tracing performance and accuracy parameters."""
219
+
220
+ beam_power_threshold: float = 0.01
221
+ beam_area_threshold_fac: float = 0.01
222
+ cutoff: float = 0.999
223
+ max_rec: int = 10
224
+ max_tir: int = 10
225
+
226
+ def __post_init__(self):
227
+ """Validate beam tracing parameters."""
228
+ if self.beam_power_threshold <= 0 or self.beam_power_threshold > 1:
229
+ raise ValueError(
230
+ f"beam_power_threshold must be in range (0, 1], got {self.beam_power_threshold}"
231
+ )
232
+
233
+ if self.beam_area_threshold_fac <= 0:
234
+ raise ValueError(
235
+ f"beam_area_threshold_fac must be positive, got {self.beam_area_threshold_fac}"
236
+ )
237
+
238
+ if self.cutoff < 0 or self.cutoff > 1:
239
+ raise ValueError(f"cutoff must be between 0 and 1, got {self.cutoff}")
240
+
241
+ if self.max_rec < 0:
242
+ raise ValueError(f"max_rec must be non-negative, got {self.max_rec}")
243
+
244
+ if self.max_tir < 0:
245
+ raise ValueError(f"max_tir must be non-negative, got {self.max_tir}")
246
+
247
+
248
+ @dataclass
249
+ class GeometryTransformConfig:
250
+ """Geometry transformation parameters.
251
+
252
+ Attributes:
253
+ scale: Problem scaling factor - scales the entire problem including geometry,
254
+ wavelength, and beam area thresholds (default: 1.0)
255
+ distortion: Geometry distortion factor (optional)
256
+ geom_scale: Per-axis geometry scaling [x, y, z] - scales only the geometry
257
+ in each dimension independently (optional)
258
+ """
259
+
260
+ scale: float = 1.0
261
+ distortion: Optional[float] = None
262
+ geom_scale: Optional[List[float]] = None
263
+
264
+ def __post_init__(self):
265
+ """Validate geometry transformations."""
266
+ if self.scale <= 0:
267
+ raise ValueError(f"scale must be positive, got {self.scale}")
268
+
269
+ if self.geom_scale is not None:
270
+ if len(self.geom_scale) != 3:
271
+ raise ValueError(
272
+ f"geom_scale must have exactly 3 values [x, y, z], "
273
+ f"got {len(self.geom_scale)}"
274
+ )
275
+ if any(s <= 0 for s in self.geom_scale):
276
+ raise ValueError("All geom_scale values must be positive")
277
+
278
+
279
+ @dataclass
280
+ class AdvancedConfig:
281
+ """Advanced optical calculation parameters."""
282
+
283
+ mapping: Optional[Any] = (
284
+ "ApertureDiffraction" # String that will be converted to enum in __post_init__
285
+ )
286
+ coherence: bool = True
287
+ fov_factor: Optional[float] = None
288
+
289
+ def __post_init__(self):
290
+ """Validate advanced optics settings and convert mapping string to enum."""
291
+ # Convert string mapping to enum if needed
292
+ if isinstance(self.mapping, str):
293
+ if self.mapping == "ApertureDiffraction":
294
+ self.mapping = goad.Mapping.ApertureDiffraction
295
+ elif self.mapping == "GeometricOptics":
296
+ self.mapping = goad.Mapping.GeometricOptics
297
+ else:
298
+ raise ValueError(
299
+ f"Invalid mapping '{self.mapping}'. Must be 'ApertureDiffraction' or 'GeometricOptics'"
300
+ )
301
+
302
+ if self.fov_factor is not None and self.fov_factor <= 0:
303
+ raise ValueError(f"fov_factor must be positive, got {self.fov_factor}")
304
+
305
+
214
306
  @dataclass
215
307
  class ConvergenceConfig:
216
308
  """
@@ -220,6 +312,29 @@ class ConvergenceConfig:
220
312
  - Standard convergence (integrated parameters, Mueller elements)
221
313
  - PHIPS detector convergence
222
314
  - Single geometry or ensemble averaging
315
+
316
+ Attributes:
317
+ geometry: Path to .obj file or directory of .obj files (ensemble)
318
+ mode: ConvergenceMode instance (StandardMode or PHIPSMode)
319
+ convergence_targets: List of convergence target dicts
320
+
321
+ wavelength: Wavelength in microns (default: 0.532)
322
+ particle_refr_index_re: Real part of particle refractive index (default: 1.31)
323
+ particle_refr_index_im: Imaginary part of particle refractive index (default: 0.0)
324
+ medium_refr_index_re: Real part of medium refractive index (default: 1.0)
325
+ medium_refr_index_im: Imaginary part of medium refractive index (default: 0.0)
326
+
327
+ batch_size: Orientations per batch (default: 24)
328
+ max_orientations: Maximum orientations (default: 100,000)
329
+ min_batches: Minimum batches before convergence check (default: 10)
330
+
331
+ beam_tracing: BeamTracingConfig instance for beam tracing parameters
332
+ geometry_transform: GeometryTransformConfig instance for geometry transformations
333
+ advanced_config: AdvancedConfig instance for advanced optical parameters
334
+ seed: Random seed for reproducibility (optional)
335
+
336
+ mueller_1d: Compute 1D Mueller matrix (default: True, standard mode only)
337
+ output_dir: Output directory path (optional)
223
338
  """
224
339
 
225
340
  # Required fields
@@ -239,6 +354,20 @@ class ConvergenceConfig:
239
354
  max_orientations: int = 100_000
240
355
  min_batches: int = 10
241
356
 
357
+ # Beam tracing configuration
358
+ beam_tracing: BeamTracingConfig = field(default_factory=BeamTracingConfig)
359
+
360
+ # Geometry transformations
361
+ geometry_transform: GeometryTransformConfig = field(
362
+ default_factory=GeometryTransformConfig
363
+ )
364
+
365
+ # Advanced optics
366
+ advanced_config: AdvancedConfig = field(default_factory=AdvancedConfig)
367
+
368
+ # Random seed for reproducibility
369
+ seed: Optional[int] = None
370
+
242
371
  # Mueller matrix output (only for StandardMode)
243
372
  mueller_1d: bool = True
244
373
 
@@ -312,6 +441,24 @@ class ConvergenceConfig:
312
441
  "batch_size": self.batch_size,
313
442
  "max_orientations": self.max_orientations,
314
443
  "min_batches": self.min_batches,
444
+ "beam_tracing": {
445
+ "beam_power_threshold": self.beam_tracing.beam_power_threshold,
446
+ "beam_area_threshold_fac": self.beam_tracing.beam_area_threshold_fac,
447
+ "cutoff": self.beam_tracing.cutoff,
448
+ "max_rec": self.beam_tracing.max_rec,
449
+ "max_tir": self.beam_tracing.max_tir,
450
+ },
451
+ "geometry_transform": {
452
+ "scale": self.geometry_transform.scale,
453
+ "distortion": self.geometry_transform.distortion,
454
+ "geom_scale": self.geometry_transform.geom_scale,
455
+ },
456
+ "advanced_config": {
457
+ "mapping": str(self.advanced_config.mapping),
458
+ "coherence": self.advanced_config.coherence,
459
+ "fov_factor": self.advanced_config.fov_factor,
460
+ },
461
+ "seed": self.seed,
315
462
  "mueller_1d": self.mueller_1d,
316
463
  "is_ensemble": self.is_ensemble(),
317
464
  }
@@ -664,7 +811,7 @@ class UnifiedConvergence:
664
811
  else:
665
812
  geom_path_str = str(self.config.geometry)
666
813
 
667
- # Create GOAD settings
814
+ # Create GOAD settings with all parameters
668
815
  settings = goad.Settings(
669
816
  geom_path=geom_path_str,
670
817
  wavelength=self.config.wavelength,
@@ -673,8 +820,32 @@ class UnifiedConvergence:
673
820
  medium_refr_index_re=self.config.medium_refr_index_re,
674
821
  medium_refr_index_im=self.config.medium_refr_index_im,
675
822
  binning=self.config.mode.get_binning(),
823
+ # Beam tracing parameters
824
+ beam_power_threshold=self.config.beam_tracing.beam_power_threshold,
825
+ beam_area_threshold_fac=self.config.beam_tracing.beam_area_threshold_fac,
826
+ cutoff=self.config.beam_tracing.cutoff,
827
+ max_rec=self.config.beam_tracing.max_rec,
828
+ max_tir=self.config.beam_tracing.max_tir,
829
+ # Geometry transformations
830
+ scale=self.config.geometry_transform.scale,
831
+ # Advanced configuration
832
+ mapping=self.config.advanced_config.mapping,
833
+ coherence=self.config.advanced_config.coherence,
676
834
  )
677
835
 
836
+ # Set optional parameters if provided
837
+ if self.config.seed is not None:
838
+ settings.seed = self.config.seed
839
+
840
+ if self.config.geometry_transform.distortion is not None:
841
+ settings.distortion = self.config.geometry_transform.distortion
842
+
843
+ if self.config.geometry_transform.geom_scale is not None:
844
+ settings.geom_scale = self.config.geometry_transform.geom_scale
845
+
846
+ if self.config.advanced_config.fov_factor is not None:
847
+ settings.fov_factor = self.config.advanced_config.fov_factor
848
+
678
849
  # Create convergence instance based on mode
679
850
  if isinstance(self.config.mode, StandardMode):
680
851
  self._setup_standard(settings)
@@ -881,19 +1052,46 @@ def run_convergence(
881
1052
  tolerance_type: "relative" or "absolute"
882
1053
  mode: ConvergenceMode instance, or string "auto"/"standard"/"phips"
883
1054
  **kwargs: Additional settings:
1055
+ # Optical settings
884
1056
  - wavelength: Wavelength in microns (default: 0.532)
885
1057
  - particle_refr_index_re: Real part of particle refractive index
886
1058
  - particle_refr_index_im: Imaginary part of particle refractive index
887
1059
  - medium_refr_index_re: Real part of medium refractive index
888
1060
  - medium_refr_index_im: Imaginary part of medium refractive index
1061
+
1062
+ # Convergence parameters
889
1063
  - batch_size: Orientations per batch (default: 24)
890
1064
  - max_orientations: Maximum orientations (default: 100,000)
891
1065
  - min_batches: Minimum batches before convergence (default: 10)
892
1066
  - mueller_1d: Compute 1D Mueller matrix (default: True, standard mode only)
1067
+
1068
+ # Mode settings
893
1069
  - phips_bins_file: Path to PHIPS bins TOML (required if mode="phips")
894
1070
  - n_theta: Number of theta bins for standard mode (default: 181)
895
1071
  - n_phi: Number of phi bins for standard mode (default: 181)
896
1072
 
1073
+ # Beam tracing parameters
1074
+ - beam_power_threshold: Beam power threshold (default: 0.05)
1075
+ - beam_area_threshold_fac: Beam area threshold factor (default: 4.0)
1076
+ - cutoff: Ray power cutoff (default: 0.001)
1077
+ - max_rec: Max recursion depth (default: 100)
1078
+ - max_tir: Max TIR bounces (default: 100)
1079
+
1080
+ # Geometry transformations
1081
+ - scale: Problem scaling factor - scales entire problem including geometry,
1082
+ wavelength, and beam area thresholds (default: 1.0)
1083
+ - distortion: Geometry distortion factor (optional)
1084
+ - geom_scale: Per-axis geometry scaling [x, y, z] - scales only geometry
1085
+ in each dimension independently (optional)
1086
+
1087
+ # Advanced configuration
1088
+ - mapping: DSCS mapping scheme (default: goad.Mapping.ApertureDiffraction)
1089
+ - coherence: Enable coherent scattering (default: False)
1090
+ - fov_factor: Field of view factor (optional)
1091
+
1092
+ # Reproducibility
1093
+ - seed: Random seed for orientations (optional)
1094
+
897
1095
  Returns:
898
1096
  UnifiedResults object
899
1097
 
@@ -924,7 +1122,44 @@ def run_convergence(
924
1122
  [{"variable": "S11", "tolerance": 0.1, "theta_indices": [180]}],
925
1123
  batch_size=12
926
1124
  )
1125
+
1126
+ # Advanced: custom beam tracing and geometry scaling
1127
+ results = run_convergence(
1128
+ "complex_particle.obj",
1129
+ "asymmetry",
1130
+ max_rec=200,
1131
+ max_tir=150,
1132
+ cutoff=0.0001,
1133
+ scale=2.0,
1134
+ seed=42
1135
+ )
927
1136
  """
1137
+ # Extract beam tracing parameters from kwargs
1138
+ beam_tracing = BeamTracingConfig(
1139
+ beam_power_threshold=kwargs.pop("beam_power_threshold", 0.05),
1140
+ beam_area_threshold_fac=kwargs.pop("beam_area_threshold_fac", 4.0),
1141
+ cutoff=kwargs.pop("cutoff", 0.001),
1142
+ max_rec=kwargs.pop("max_rec", 100),
1143
+ max_tir=kwargs.pop("max_tir", 100),
1144
+ )
1145
+
1146
+ # Extract geometry transform parameters
1147
+ geometry_transform = GeometryTransformConfig(
1148
+ scale=kwargs.pop("scale", 1.0),
1149
+ distortion=kwargs.pop("distortion", None),
1150
+ geom_scale=kwargs.pop("geom_scale", None),
1151
+ )
1152
+
1153
+ # Extract advanced configuration parameters
1154
+ advanced_config = AdvancedConfig(
1155
+ mapping=kwargs.pop("mapping", "ApertureDiffraction"),
1156
+ coherence=kwargs.pop("coherence", False),
1157
+ fov_factor=kwargs.pop("fov_factor", None),
1158
+ )
1159
+
1160
+ # Extract seed
1161
+ seed = kwargs.pop("seed", None)
1162
+
928
1163
  # Normalize targets to list of dicts
929
1164
  target_dicts = _normalize_targets(targets, tolerance, tolerance_type)
930
1165
 
@@ -946,9 +1181,16 @@ def run_convergence(
946
1181
  f"Invalid mode string '{mode}'. Must be 'auto', 'standard', or 'phips'"
947
1182
  )
948
1183
 
949
- # Build config
1184
+ # Build config with new parameters
950
1185
  config = ConvergenceConfig(
951
- geometry=geometry, mode=mode, convergence_targets=target_dicts, **kwargs
1186
+ geometry=geometry,
1187
+ mode=mode,
1188
+ convergence_targets=target_dicts,
1189
+ beam_tracing=beam_tracing,
1190
+ geometry_transform=geometry_transform,
1191
+ advanced_config=advanced_config,
1192
+ seed=seed,
1193
+ **kwargs, # Remaining kwargs (wavelength, particle_refr_index_re, etc.)
952
1194
  )
953
1195
 
954
1196
  # Run
@@ -1,6 +1,7 @@
1
1
  use goad::{
2
2
  self,
3
3
  bins::BinningScheme,
4
+ diff::Mapping,
4
5
  geom::Geom,
5
6
  geom::Shape,
6
7
  multiproblem::MultiProblem,
@@ -75,6 +76,9 @@ fn _goad_py(m: &Bound<'_, PyModule>) -> PyResult<()> {
75
76
  m.add_class::<Orientation>()?;
76
77
  m.add_class::<Scheme>()?;
77
78
 
79
+ // Mapping enum
80
+ m.add_class::<Mapping>()?;
81
+
78
82
  // Helper functions for orientations
79
83
  m.add_function(wrap_pyfunction!(uniform_orientation, m)?)?;
80
84
  m.add_function(wrap_pyfunction!(discrete_orientation, m)?)?;
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "goad-py"
7
- version = "0.5.6"
7
+ version = "0.6.1"
8
8
  description = "Physical optics light scattering computation"
9
9
  authors = [{name = "Harry Ballington", email = "ballington@uni-wuppertal.de"}]
10
10
  license = {text = "GPL-3.0"}