BornSim 0.2.6__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.
Files changed (110) hide show
  1. bornsim-0.2.6/.coveragerc +17 -0
  2. bornsim-0.2.6/.pre-commit-config.yaml +18 -0
  3. bornsim-0.2.6/.zenodo.json +18 -0
  4. bornsim-0.2.6/BornSim.egg-info/PKG-INFO +529 -0
  5. bornsim-0.2.6/BornSim.egg-info/SOURCES.txt +108 -0
  6. bornsim-0.2.6/BornSim.egg-info/dependency_links.txt +1 -0
  7. bornsim-0.2.6/BornSim.egg-info/requires.txt +24 -0
  8. bornsim-0.2.6/BornSim.egg-info/top_level.txt +1 -0
  9. bornsim-0.2.6/CITATION.cff +17 -0
  10. bornsim-0.2.6/CONTRIBUTING.md +23 -0
  11. bornsim-0.2.6/LICENSE +21 -0
  12. bornsim-0.2.6/MANIFEST.in +11 -0
  13. bornsim-0.2.6/PKG-INFO +529 -0
  14. bornsim-0.2.6/README.rst +488 -0
  15. bornsim-0.2.6/bornsim/__init__.py +69 -0
  16. bornsim-0.2.6/bornsim/_archives.py +172 -0
  17. bornsim-0.2.6/bornsim/_result_plotting.py +454 -0
  18. bornsim-0.2.6/bornsim/_result_validation.py +89 -0
  19. bornsim-0.2.6/bornsim/_validation.py +10 -0
  20. bornsim-0.2.6/bornsim/_version.py +1 -0
  21. bornsim-0.2.6/bornsim/_volume_plotting.py +395 -0
  22. bornsim-0.2.6/bornsim/angular_data.py +338 -0
  23. bornsim-0.2.6/bornsim/api.py +26 -0
  24. bornsim-0.2.6/bornsim/directions.py +99 -0
  25. bornsim-0.2.6/bornsim/ensemble.py +253 -0
  26. bornsim-0.2.6/bornsim/ensemble_sampling.py +75 -0
  27. bornsim-0.2.6/bornsim/geometry.py +716 -0
  28. bornsim-0.2.6/bornsim/green.py +161 -0
  29. bornsim-0.2.6/bornsim/grid.py +98 -0
  30. bornsim-0.2.6/bornsim/material.py +46 -0
  31. bornsim-0.2.6/bornsim/media.py +359 -0
  32. bornsim-0.2.6/bornsim/model.py +276 -0
  33. bornsim-0.2.6/bornsim/results.py +717 -0
  34. bornsim-0.2.6/bornsim/rotation.py +68 -0
  35. bornsim-0.2.6/bornsim/sampling.py +231 -0
  36. bornsim-0.2.6/bornsim/series.py +300 -0
  37. bornsim-0.2.6/bornsim/solver.py +561 -0
  38. bornsim-0.2.6/bornsim/source.py +57 -0
  39. bornsim-0.2.6/bornsim/units.py +81 -0
  40. bornsim-0.2.6/bornsim/volume.py +319 -0
  41. bornsim-0.2.6/conda.recipe/meta.yaml +36 -0
  42. bornsim-0.2.6/docs/examples/README.rst +22 -0
  43. bornsim-0.2.6/docs/examples/born_orders/README.rst +5 -0
  44. bornsim-0.2.6/docs/examples/born_orders/born_interference.py +120 -0
  45. bornsim-0.2.6/docs/examples/born_orders/compare_orders.py +80 -0
  46. bornsim-0.2.6/docs/examples/random_media/README.rst +5 -0
  47. bornsim-0.2.6/docs/examples/random_media/analytical_scattering.py +28 -0
  48. bornsim-0.2.6/docs/examples/random_media/covariance_models.py +90 -0
  49. bornsim-0.2.6/docs/examples/random_media/phase_function.py +81 -0
  50. bornsim-0.2.6/docs/examples/random_media/random_medium.py +154 -0
  51. bornsim-0.2.6/docs/examples/random_media/theory_random_fields.py +195 -0
  52. bornsim-0.2.6/docs/examples/random_media/wavelength_dependence.py +85 -0
  53. bornsim-0.2.6/docs/examples/results/README.rst +5 -0
  54. bornsim-0.2.6/docs/examples/results/result_plots.py +118 -0
  55. bornsim-0.2.6/docs/examples/results/save_load_results.py +104 -0
  56. bornsim-0.2.6/docs/examples/structured_media/README.rst +5 -0
  57. bornsim-0.2.6/docs/examples/structured_media/dielectric_sphere.py +139 -0
  58. bornsim-0.2.6/docs/examples/structured_media/structured_media.py +152 -0
  59. bornsim-0.2.6/docs/examples/structured_media/theory_directional_interference.py +146 -0
  60. bornsim-0.2.6/docs/examples/validation/README.rst +5 -0
  61. bornsim-0.2.6/docs/examples/validation/angular_convergence.py +140 -0
  62. bornsim-0.2.6/docs/examples/validation/ensemble_sampling.py +128 -0
  63. bornsim-0.2.6/docs/examples/validation/finite_size_comparison.py +114 -0
  64. bornsim-0.2.6/docs/examples/validation/grid_refinement.py +102 -0
  65. bornsim-0.2.6/docs/images/branding-prompts.rst +21 -0
  66. bornsim-0.2.6/docs/source/_static/favicon.png +0 -0
  67. bornsim-0.2.6/docs/source/_static/logo.png +0 -0
  68. bornsim-0.2.6/docs/source/_static/phase_function.svg +2429 -0
  69. bornsim-0.2.6/docs/source/api.rst +442 -0
  70. bornsim-0.2.6/docs/source/conf.py +154 -0
  71. bornsim-0.2.6/docs/source/development.rst +89 -0
  72. bornsim-0.2.6/docs/source/examples.rst +221 -0
  73. bornsim-0.2.6/docs/source/gallery_config.py +53 -0
  74. bornsim-0.2.6/docs/source/getting_started.rst +99 -0
  75. bornsim-0.2.6/docs/source/guide.rst +28 -0
  76. bornsim-0.2.6/docs/source/index.rst +50 -0
  77. bornsim-0.2.6/docs/source/medium_visualization.rst +358 -0
  78. bornsim-0.2.6/docs/source/overview.rst +2 -0
  79. bornsim-0.2.6/docs/source/resources.rst +41 -0
  80. bornsim-0.2.6/docs/source/theory.rst +566 -0
  81. bornsim-0.2.6/makefile +71 -0
  82. bornsim-0.2.6/pyproject.toml +52 -0
  83. bornsim-0.2.6/pytest.ini +19 -0
  84. bornsim-0.2.6/setup.cfg +4 -0
  85. bornsim-0.2.6/tests/analytical/test_api.py +363 -0
  86. bornsim-0.2.6/tests/analytical/test_model.py +169 -0
  87. bornsim-0.2.6/tests/analytical/test_result.py +242 -0
  88. bornsim-0.2.6/tests/analytical/test_units.py +289 -0
  89. bornsim-0.2.6/tests/conftest.py +15 -0
  90. bornsim-0.2.6/tests/numerical/test_api.py +215 -0
  91. bornsim-0.2.6/tests/numerical/test_api_keywords.py +120 -0
  92. bornsim-0.2.6/tests/numerical/test_api_ownership.py +263 -0
  93. bornsim-0.2.6/tests/numerical/test_configuration.py +443 -0
  94. bornsim-0.2.6/tests/numerical/test_directional_api.py +301 -0
  95. bornsim-0.2.6/tests/numerical/test_directions.py +130 -0
  96. bornsim-0.2.6/tests/numerical/test_engine.py +164 -0
  97. bornsim-0.2.6/tests/numerical/test_explicit_physics.py +108 -0
  98. bornsim-0.2.6/tests/numerical/test_geometry.py +478 -0
  99. bornsim-0.2.6/tests/numerical/test_media.py +371 -0
  100. bornsim-0.2.6/tests/numerical/test_medium_composition.py +238 -0
  101. bornsim-0.2.6/tests/numerical/test_medium_metadata.py +123 -0
  102. bornsim-0.2.6/tests/numerical/test_result.py +462 -0
  103. bornsim-0.2.6/tests/numerical/test_series.py +591 -0
  104. bornsim-0.2.6/tests/numerical/test_units.py +622 -0
  105. bornsim-0.2.6/tests/numerical/test_volume_plotting.py +444 -0
  106. bornsim-0.2.6/tests/packaging/test_release_tools.py +158 -0
  107. bornsim-0.2.6/tools/check_release.py +115 -0
  108. bornsim-0.2.6/tools/check_wheel.py +73 -0
  109. bornsim-0.2.6/tools/next_release_version.py +81 -0
  110. bornsim-0.2.6/tools/release_tag.py +201 -0
@@ -0,0 +1,17 @@
1
+ [run]
2
+ source = bornsim
3
+ branch = True
4
+ relative_files = True
5
+ omit =
6
+ bornsim/_version.py
7
+
8
+ [report]
9
+ skip_covered = True
10
+ show_missing = True
11
+ precision = 2
12
+
13
+ [html]
14
+ directory = htmlcov
15
+
16
+ [xml]
17
+ output = coverage.xml
@@ -0,0 +1,18 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.6.0
4
+ hooks:
5
+ - id: check-merge-conflict
6
+ - id: end-of-file-fixer
7
+ - id: mixed-line-ending
8
+ - id: check-yaml
9
+ exclude: ^conda.recipe/meta.yaml$
10
+ - id: check-toml
11
+ - id: detect-private-key
12
+ - id: check-json
13
+ - id: check-case-conflict
14
+ - repo: https://github.com/astral-sh/ruff-pre-commit
15
+ rev: v0.11.2
16
+ hooks:
17
+ - id: ruff
18
+ - id: ruff-format
@@ -0,0 +1,18 @@
1
+ {
2
+ "title": "BornSim",
3
+ "upload_type": "software",
4
+ "description": "Vector Born-series scattering from continuous random dielectric media.",
5
+ "creators": [
6
+ {
7
+ "name": "Poinsinet de Sivry-Houle, Martin"
8
+ }
9
+ ],
10
+ "license": "mit",
11
+ "version": "0.2.6",
12
+ "keywords": [
13
+ "Born approximation",
14
+ "light scattering",
15
+ "random media"
16
+ ],
17
+ "publication_date": "2026-10-09"
18
+ }
@@ -0,0 +1,529 @@
1
+ Metadata-Version: 2.4
2
+ Name: BornSim
3
+ Version: 0.2.6
4
+ Summary: Vector Born-series scattering from continuous random dielectric media
5
+ Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
6
+ License-Expression: MIT
7
+ Keywords: born approximation,light scattering,random media,tissue optics
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Scientific/Engineering :: Physics
16
+ Classifier: Topic :: Scientific/Engineering :: Visualization
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/x-rst
19
+ License-File: LICENSE
20
+ Requires-Dist: numpy<3,>=1.24
21
+ Requires-Dist: TypedUnit==0.0.11
22
+ Requires-Dist: matplotlib<4,>=3.7
23
+ Requires-Dist: plotly<7,>=6
24
+ Provides-Extra: testing
25
+ Requires-Dist: pytest<10,>=8; extra == "testing"
26
+ Requires-Dist: pytest-cov<8,>=5; extra == "testing"
27
+ Provides-Extra: test
28
+ Requires-Dist: BornSim[testing]; extra == "test"
29
+ Provides-Extra: documentation
30
+ Requires-Dist: sphinx<10,>=7; extra == "documentation"
31
+ Requires-Dist: pydata-sphinx-theme<0.20,>=0.16; extra == "documentation"
32
+ Requires-Dist: sphinx-gallery<1,>=0.19; extra == "documentation"
33
+ Requires-Dist: sphinx-design<1,>=0.6; extra == "documentation"
34
+ Provides-Extra: dev
35
+ Requires-Dist: ruff<1,>=0.11; extra == "dev"
36
+ Requires-Dist: mypy<2,>=1.11; extra == "dev"
37
+ Requires-Dist: build<2,>=1.2; extra == "dev"
38
+ Requires-Dist: twine<7,>=6; extra == "dev"
39
+ Requires-Dist: pre-commit<5,>=4; extra == "dev"
40
+ Dynamic: license-file
41
+
42
+ .. image:: docs/source/_static/logo.png
43
+ :alt: BornSim logo
44
+ :width: 420
45
+
46
+ .. documentation-content-start
47
+
48
+ .. list-table::
49
+ :widths: 35 65
50
+ :header-rows: 1
51
+
52
+ * - Badge
53
+ - Status
54
+ * - Python versions
55
+ - |python|
56
+ * - Documentation
57
+ - |docs|
58
+ * - Continuous integration
59
+ - |tests|
60
+ * - Static quality checks
61
+ - |quality|
62
+ * - Test coverage
63
+ - |coverage|
64
+ * - Latest release tag
65
+ - |release|
66
+ * - Package publication
67
+ - |publication|
68
+ * - License
69
+ - |license|
70
+
71
+ BornSim
72
+ ========
73
+
74
+ A Python package for vector Born-series scattering from continuous, isotropic refractive-index fluctuations, such as idealized tissue models.
75
+
76
+ Install
77
+ -------
78
+
79
+ Requires Python 3.11 or newer.
80
+
81
+ .. code-block:: console
82
+
83
+ python -m venv .venv
84
+ .venv/bin/python -m pip install -e .
85
+
86
+
87
+ BornSim constructor, function, and method arguments are keyword-only, except
88
+ ``add_structures(*structures)``, which takes positional shape objects.
89
+ Calls with multiple arguments use one argument per line and a trailing comma.
90
+
91
+ The Python API accepts TypedUnit quantities; dimensional inputs require explicit units. The wavelength is the **vacuum** wavelength. Outputs are angular differential scattering, μs, anisotropy g, and μs′, with scattering coefficients in inverse metres.
92
+
93
+ .. code-block:: python
94
+
95
+ import matplotlib.pyplot as plt
96
+ from bornsim import EnsembleSampling, Grid, RandomMedium, Source, Solver
97
+ from bornsim.units import ureg
98
+
99
+ grid = Grid(
100
+ shape=(8, 8, 8),
101
+ spacing=50 * ureg.nanometer,
102
+ )
103
+
104
+ medium = RandomMedium(
105
+ background_refractive_index=1.33,
106
+ refractive_index_std=0.01,
107
+ correlation_length=100 * ureg.nanometer,
108
+ correlation="gaussian",
109
+ )
110
+
111
+ solver = Solver(
112
+ source=Source(wavelength=633 * ureg.nanometer),
113
+ order=3,
114
+ )
115
+
116
+ result = solver.ensemble(
117
+ medium=medium,
118
+ grid=grid,
119
+ ensemble_sampling=EnsembleSampling(
120
+ realizations=4,
121
+ seed=42,
122
+ ),
123
+ )
124
+
125
+ print(
126
+ f"mu_s = {result.mu_s.to('1 / millimeter')}, "
127
+ f"g = {result.g}, "
128
+ f"mu_s_prime = {result.mu_s_prime.to('1 / millimeter')}"
129
+ )
130
+
131
+ result.plot()
132
+
133
+ plt.show()
134
+
135
+ ``Source`` describes the supported unpolarized plane wave propagating along +z.
136
+ ``RandomMedium`` defines numerical random-field statistics.
137
+ ``Grid`` defines spatial shape and spacing once, and is shared by
138
+ ``medium.to_volume(grid=grid)`` and ``solver.ensemble(grid=grid, ...)``.
139
+ Every ``Volume`` retains its grid. ``AngularSampling`` groups output polar
140
+ angles, polar integration nodes and azimuth resolution; pass it as
141
+ ``Solver(sampling=sampling, ...)`` to share settings across calculations.
142
+
143
+ ``Solver.solve(target=volume)`` computes full directional scattering and
144
+ solid-angle integrals by default, providing normalized 3D phase functions
145
+ for one fixed sample. Use ``solve_cut`` with explicit ``angles`` for an x-z
146
+ angular cut, or explicit unit ``directions`` for arbitrary observations. Neither cut
147
+ supplies integrated coefficients. Angle quantities may use degrees or radians.
148
+ ``Solver.ensemble(medium=medium, grid=grid, ensemble_sampling=...)`` averages
149
+ independent seeded random volumes and returns standard errors. It accepts
150
+ random media and structures with a random background. Deterministic
151
+ structures generate a volume and use ``solve`` directly.
152
+
153
+ These calculations return a ``Result`` whose ``differential`` array has
154
+ shape (order, polar angle, azimuth) for full calculations. Its ``plot()`` returns a Matplotlib figure, with
155
+ one-standard-error bars for ensembles; ``plot(terms=True)`` shows isolated
156
+ numerical terms without interference or error bars. Call ``plt.show()`` to display the figures or
157
+ ``figure.savefig("scattering.svg")`` to export a figure. Full-volume and
158
+ ensemble coefficients describe finite samples and remain distinct from
159
+ infinite-medium analytical ones.
160
+ Undefined anisotropy is ``NaN`` in this object API. Existing functions and
161
+ their return formats remain available.
162
+
163
+ ``result.phase_function`` normalizes every sampled direction using its full
164
+ solid-angle integral. Full ``differential`` data have shape
165
+ ``(order, polar angle, azimuth)``. Curves select one sampled meridian;
166
+ ``result.azimuth_average()`` explicitly requests averaged intensities.
167
+ ``result.plot_phase_function(view="3d")`` preserves directional asymmetry.
168
+ For notebook rotation, install the notebook extra and use
169
+ ``%matplotlib widget``. ``result.amplitudes`` retains complex amplitudes with
170
+ the same observation axes followed by polarization and Cartesian axes.
171
+ Use ``solver.solve_cut`` for unnormalized angular cuts, and
172
+ ``result.plot_cross_section()`` for finite-sample area per steradian without
173
+ passing the original Volume. Saved results retain the physical sample volume.
174
+
175
+ ``result.plot_field_norms()`` shows numerical field terms per realization.
176
+ See ``docs/examples/results/result_plots.py`` for direct plotting examples in the
177
+ Sphinx Gallery documentation.
178
+
179
+ Results validate array shapes and physical ranges at construction.
180
+ ``result.provenance`` records calculation settings, versions, seeds, grid
181
+ and quadrature information. ``result.save(path="scattering.npz")`` and
182
+ ``Result.load(path="scattering.npz")`` preserve units, complex amplitudes,
183
+ uncertainty, and metadata without pickle. Input voxel fields are not stored;
184
+ retain manually supplied fields separately. See the numerical validation
185
+ gallery for independent refinement and sampling checks.
186
+
187
+ Units
188
+ -----
189
+
190
+ ``bornsim.units`` exposes TypedUnit's shared ``ureg``, ``Quantity``, ``Length``,
191
+ ``Angle``, ``Dimensionless``, and ``RefractiveIndex``, using the same registry
192
+ as PyMieSim. Physical inputs accept compatible units and reject incompatible
193
+ dimensions. Lengths and angles require explicit units. Refractive indices,
194
+ their standard deviations and fluctuation arrays require plain numbers without
195
+ units, including when passed to Material or shape constructors.
196
+ Physical configuration has no arbitrary defaults. Supply wavelengths,
197
+ refractive indices, fluctuation statistics, covariance choice, grid shape and
198
+ voxel spacing explicitly. ``StructuredMedium()`` starts as an empty builder;
199
+ call ``add_background(...)`` before generating a volume. Numerical controls
200
+ retain defaults, and zero centres and identity shape rotations define the
201
+ neutral coordinate conventions.
202
+
203
+ Physical settings and coordinates retain quantities in the supplied units.
204
+ Validation checks units and scalar shape without converting them. FFT and
205
+ Fourier kernels extract explicit SI magnitudes where numeric arrays are needed.
206
+
207
+ ``Source.wavelength`` and every numerical array in ``Result`` are quantities.
208
+ Angles, amplitudes, scattering coefficients and uncertainties retain physical
209
+ units. Saved archives use radians, metres and inverse metres explicitly. Anisotropy, direction vectors, and relative
210
+ field norms are dimensionless. Use ``.to("unit")`` to convert and
211
+ ``.magnitude`` to retrieve an array. Plots convert to degrees and SI
212
+ scattering units explicitly, including uncertainty bars.
213
+
214
+ Analytical functions return unit-bearing scattering coefficients.
215
+ ``random_volume`` retains unit-bearing voxel spacing and coordinates.
216
+ The low-level Born and ensemble kernels document their numeric SI outputs.
217
+
218
+
219
+ Physical model
220
+ --------------
221
+
222
+ Real-valued refractive index fluctuations are linearized as δε ≈ 2 n₀ δn. For an unpolarized incident wave, the differential scattering coefficient is k₀⁴ Φε(q) (1 + cos²θ)/(32π²), where q = 2 n₀ k₀ sin(θ/2), k₀ = 2π/λvac and Φε is the three-dimensional Fourier transform of the dielectric covariance without a Fourier normalization prefactor.
223
+
224
+ The refractive index covariance is σn² exp(−r²/(2ℓ²)) for the Gaussian model and σn² exp(−r/ℓ) for the exponential model. These definitions matter when comparing correlation lengths between publications. Gauss–Legendre quadrature integrates over solid angle. Increase ``quadrature_order`` to check convergence for strongly forward-peaked scattering.
225
+
226
+ The analytical solver is a first-order, single-scattering model. The numerical solver includes repeated interactions through the chosen Born order. No slab-transmission observable or particle-packing generator is provided;
227
+ fixed particle configurations can be composed and propagated numerically. A dense medium can still have weak fluctuations; density alone does not establish Born validity. Contrast, correlation length, wavelength, and propagation distance matter. Decreasing Born terms do not certify convergence.
228
+
229
+ Scientific references:
230
+
231
+ - `Nonscalar elastic light scattering from continuous media in the Born approximation <https://pmc.ncbi.nlm.nih.gov/articles/PMC3839346/>`_.
232
+ - `Accuracy of the Born approximation in calculating the scattering coefficient of biological continuous random media <https://opg.optica.org/ol/abstract.cfm?uri=ol-34-17-2679>`_.
233
+
234
+ Check
235
+ -----
236
+
237
+ .. code-block:: console
238
+
239
+ .venv/bin/python -m pytest tests
240
+
241
+
242
+ Tests cover the analytic short-correlation limit, contrast scaling, independent angular integration, and input validation.
243
+
244
+ Numerical media and geometry
245
+ ----------------------------
246
+
247
+ ``RandomMedium`` generates numerical Gaussian random fields with Gaussian,
248
+ exponential, or Whittle–Matérn spatial covariance. Matérn ``smoothness`` uses
249
+ the convention ``x = sqrt(2 * nu) * r / correlation_length``; ``nu = 0.5``
250
+ reproduces exponential covariance. ``Medium`` is an abstract base class;
251
+ instantiate ``RandomMedium`` or ``StructuredMedium`` and call ``to_volume()``.
252
+ The existing analytical statistics class is named ``AnalyticalMedium``.
253
+ Old ``Medium(...)`` calls must be replaced; choose ``correlation="gaussian"``
254
+ for the analytical Gaussian model. Choose the covariance family explicitly. Matérn models also require smoothness.
255
+
256
+ ``StructuredMedium`` voxelizes ordered ``Layer``, ``Sphere``, ``Ellipsoid``,
257
+ ``Box``, and ``Cylinder`` regions. Regions specify absolute indices; later
258
+ regions replace earlier ones in overlaps. Layers are finite slabs clipped
259
+ to the box, with a uniform background outside the sample.
260
+
261
+ .. code-block:: python
262
+
263
+ from bornsim.units import ureg
264
+
265
+ from bornsim import Grid, RandomMedium, Layer, Sphere, StructuredMedium
266
+ from bornsim.media import random_volume
267
+ from bornsim import Solver, Source
268
+
269
+ random_sample = random_volume(
270
+ medium=RandomMedium(
271
+ correlation="matern",
272
+ smoothness=1.5,
273
+ background_refractive_index=1.33,
274
+ refractive_index_std=0.01,
275
+ correlation_length=100e-9 * ureg.meter,
276
+ ),
277
+ grid=Grid(
278
+ shape=(12, 12, 12),
279
+ spacing=3e-08 * ureg.meter,
280
+ ),
281
+ seed=42,
282
+ )
283
+
284
+ structure = StructuredMedium()
285
+
286
+ structure.add_background(refractive_index=1.33)
287
+
288
+ layer = Layer(
289
+ lower=-1.8e-07 * ureg.meter,
290
+ upper=0 * ureg.meter,
291
+ refractive_index=1.34,
292
+ )
293
+
294
+ sphere = Sphere(
295
+ radius=7e-08 * ureg.meter,
296
+ refractive_index=1.345,
297
+ centre=(0, 0, 4e-08) * ureg.meter,
298
+ )
299
+
300
+ structure.add_structures(
301
+ layer,
302
+ sphere,
303
+ )
304
+
305
+ structured_sample = structure.to_volume(
306
+ grid=Grid(
307
+ shape=(12, 12, 12),
308
+ spacing=3e-08 * ureg.meter,
309
+ ),
310
+ )
311
+
312
+ solver = Solver(
313
+ source=Source(
314
+ wavelength=633e-9 * ureg.meter,
315
+ ),
316
+ order=3,
317
+ )
318
+
319
+ result = solver.solve(target=structured_sample)
320
+
321
+ ``Solver`` delegates numerical propagation to ``BornSeries``, which reuses
322
+ its Green operator across compatible volumes. Ensemble calculations build
323
+ one engine for all realizations. Voxel fields, random generation, Green
324
+ propagation, Born iteration, and ensemble statistics live in separate modules.
325
+
326
+ The numerical method uses a vector volume integral on cubic voxels, rather
327
+ than finite elements. The documentation's numerical theory section derives
328
+ field synthesis, geometry masks, Born orders, the Green self cell, and
329
+ scattering normalization.
330
+
331
+ Three-dimensional media
332
+ -----------------------
333
+
334
+ ``Volume.plot_3d()`` defaults to an interactive Plotly volume.
335
+ ``result.plot_phase_function(view="3d")`` defaults to a Plotly surface.
336
+ Select ``backend="matplotlib"`` explicitly for static 3D figures, orthogonal
337
+ slices, or sampled voxel geometry such as spheres.
338
+
339
+ .. code-block:: python
340
+
341
+ from bornsim.units import ureg
342
+
343
+ from bornsim import Grid, RandomMedium
344
+
345
+ medium = RandomMedium(
346
+ background_refractive_index=1.33,
347
+ refractive_index_std=0.01,
348
+ correlation_length=100e-9 * ureg.meter,
349
+ correlation="matern",
350
+ smoothness=1.5,
351
+ )
352
+
353
+ volume = medium.to_volume(
354
+ grid=Grid(
355
+ shape=(16, 16, 16),
356
+ spacing=2.5e-08 * ureg.meter,
357
+ ),
358
+ seed=42,
359
+ )
360
+
361
+ figure = volume.plot_3d(
362
+ field="refractive_index",
363
+ opacity_scale="increasing",
364
+ length_unit="nanometer",
365
+ )
366
+
367
+ figure.show()
368
+
369
+ Matplotlib figures rotate with an interactive backend and can be exported
370
+ with ``figure.savefig()``. The documentation gallery embeds interactive 3D volumes and phase surfaces.
371
+ Plotly is included with BornSim and is the default for 3D views. Call
372
+ ``figure.show()`` for browser interaction or ``figure.write_html()`` to export
373
+ an interactive view. Angular and polar result plots use Matplotlib.
374
+ See the medium visualization documentation for units and rendering conventions.
375
+
376
+ Numerical Born series
377
+ ---------------------
378
+
379
+ Generate a seeded random volume and evaluate cumulative Born orders, or average independent realizations with one-standard-error estimates. Isolated term intensities exclude interference and must not be summed to obtain cumulative intensity.
380
+
381
+ .. code-block:: python
382
+
383
+ from bornsim.units import ureg
384
+
385
+ import numpy as np
386
+ from bornsim import Directions, EnsembleSampling, Grid, RandomMedium
387
+ from bornsim.series import BornSeries
388
+ from bornsim.media import random_volume
389
+ from bornsim.ensemble import ensemble_scattering
390
+
391
+ medium = RandomMedium(
392
+ refractive_index_std=0.01,
393
+ correlation_length=1e-07 * ureg.meter,
394
+ correlation="gaussian",
395
+ background_refractive_index=1.33,
396
+ )
397
+
398
+ volume = random_volume(
399
+ medium=medium,
400
+ grid=Grid(
401
+ shape=(12, 12, 12),
402
+ spacing=5e-08 * ureg.meter,
403
+ ),
404
+ seed=42,
405
+ )
406
+
407
+ directions = Directions(vectors=[[0.0, 0.0, 1.0], [1.0, 0.0, 0.0]])
408
+
409
+ # directions.vectors is the immutable Cartesian array.
410
+
411
+ engine = BornSeries(
412
+ grid=volume.grid,
413
+ background_refractive_index=volume.background_refractive_index,
414
+ wavelength=6.33e-07 * ureg.meter,
415
+ directions=directions,
416
+ order=3,
417
+ )
418
+
419
+ result = engine.solve(volume=volume)
420
+
421
+ # result.amplitudes[j] is the (j+1)-th term, in metres.
422
+ # result.differential[j] includes amplitude interference through order j+1.
423
+ ensemble = ensemble_scattering(
424
+ medium=medium,
425
+ wavelength=6.33e-07 * ureg.meter,
426
+ order=3,
427
+ ensemble_sampling=EnsembleSampling(
428
+ realizations=4,
429
+ seed=42,
430
+ ),
431
+ shape=(12, 12, 12),
432
+ spacing=50e-9 * ureg.meter,
433
+ )
434
+
435
+ The API supports orders 1–12. Work limits bound synchronous calculations. Random fields have a **Gaussian probability distribution**, with Gaussian, exponential or Whittle–Matérn spatial covariance. Those are distinct choices. A spectral generator samples a doubled periodic box and crops it; ensemble point variance is normalized, but individual samples retain their random means and variances. Finite resolution truncates the spectrum, particularly for exponential covariance, and finite synthesis boxes approximate the continuum statistics.
436
+
437
+ The solver uses the outgoing dyadic background Green tensor, incident propagation along +z and an incoherent average over x/y incident polarizations. Zero-padded FFT convolution evaluates the finite-volume interactions without periodic propagation. The singular self cell uses an equal-volume sphere with the longitudinal contact term; off-diagonal cells use midpoint quadrature. This is an approximate voxel discretization, not a validated full-wave tissue simulator.
438
+
439
+ All orders use the same **linearized dielectric contrast** δε = 2 n₀ δn as the analytical model. They are perturbation orders in this dielectric contrast; the omitted δn² constitutive term is not restored by increasing Born order. Generated nonpositive dielectric values are rejected.
440
+
441
+ A first-order scattered amplitude is calculated from the incident field; subsequent amplitudes use successive applications of the Green operator. Amplitudes are summed before squaring. Ensemble averaging then averages intensities, never random amplitudes. The numerical coefficients are finite-sample cross sections divided by volume, and include the total scattered intensity; they are not automatically intrinsic infinite-medium transport coefficients. The mean anisotropy is calculated from averaged angular moments.
442
+
443
+ Numerical integration uses ``polar_samples`` Gauss nodes (default 32, range 16–128) and an azimuth count set by ``azimuth_samples`` (default 8). Plot angles are separately configurable. Check angular convergence for electrically large samples; increase ``polar_samples`` and ``azimuth_samples`` before using such samples quantitatively. Check grid refinement, sample size, synthesis-box effects and realization count as well. A single realization has unknown standard error (``NaN``). Growing field terms trigger a diagnostic; decreasing terms do not certify convergence. Modified/convergent Born series are not implemented.
444
+
445
+ Higher-order references:
446
+
447
+ - `Electromagnetic Born-series formulation and amplitude scaling <https://doi.org/10.1093/ptep/ptae008>`_.
448
+ - `Discrete-dipole and volume-integral discretization background <https://collaborate.princeton.edu/en/publications/discrete-dipole-approximation-for-scattering-calculations/>`_.
449
+ - `Modified convergent Born series, a distinct future solver <https://arxiv.org/abs/1601.05997>`_.
450
+
451
+ Regression checks compare open-boundary FFT interactions against an independently assembled dyadic matrix, compare orders 1–3 against a direct linear solve, verify per-order contrast scaling and interference, test first-order grid refinement, check random-field statistics, and verify ensemble means and standard errors.
452
+
453
+ Repository layout
454
+ -----------------
455
+
456
+ .. code-block:: text
457
+
458
+ bornsim/ Public API, numerical solvers, Matplotlib plots
459
+ tests/analytical/ First-order analytical regression tests
460
+ tests/numerical/ Independent Born-series and random-field checks
461
+ tests/packaging/ Metadata and release-tool tests
462
+ docs/source/ Sphinx documentation
463
+ docs/examples/ Runnable examples
464
+ development/ Exploratory work excluded from distributions
465
+ tools/ Release and wheel-verification commands
466
+ conda.recipe/ Conda package recipe
467
+ .github/workflows/ Quality, tests, documentation and publication checks
468
+
469
+ Development
470
+ -----------
471
+
472
+ .. code-block:: console
473
+
474
+ make editable PYTHON=.venv/bin/python
475
+ make check PYTHON=.venv/bin/python
476
+ make docs PYTHON=.venv/bin/python
477
+ make package-check PYTHON=.venv/bin/python
478
+ make release-check PYTHON=.venv/bin/python
479
+
480
+ See ``CONTRIBUTING.md`` for development and release instructions. The package
481
+ uses the MIT license in ``LICENSE``; software citation metadata is provided in
482
+ ``CITATION.cff``. The repository currently has no configured GitHub remote.
483
+
484
+ API configuration and data ownership
485
+ ------------------------------------
486
+
487
+ Use Grid, AngularSampling and EnsembleSampling to define spatial, angular and
488
+ realization settings. Legacy individual configuration keywords are deprecated.
489
+ EnsembleSampling accepts consecutive seeds or an explicit list of distinct
490
+ seeds. Material objects share a real absolute refractive index across shapes.
491
+ Results and their arrays are read-only; StructuredMedium remains mutable.
492
+ ``result.angular`` owns the directional data and is reused on every access.
493
+ ``result.meridian(azimuth=90 * ureg.degree)`` selects sampled directions using
494
+ physical angles. Exact selection is the default; nearest selection must be
495
+ requested explicitly and never interpolates or averages intensities.
496
+
497
+ Theory and simulated figures
498
+ ----------------------------
499
+
500
+ The documentation's theory section derives the vector volume integral, Born
501
+ orders, coherent amplitudes, phase normalization and ensemble uncertainty.
502
+ Simulated figures connect these equations to random-field covariance,
503
+ translation invariance, two-particle interference and voxel refinement.
504
+ The figures link to runnable examples and state their numerical limitations.
505
+
506
+ .. |python| image:: https://img.shields.io/badge/Python-3.11%2B-3776AB.svg
507
+ :alt: Python 3.11 or newer
508
+ :target: https://www.python.org/
509
+ .. |docs| image:: https://github.com/MartinPdeS/BornSim/actions/workflows/deploy_documentation.yml/badge.svg
510
+ :alt: Documentation build status
511
+ :target: https://martinpdes.github.io/BornSim/docs/latest/
512
+ .. |tests| image:: https://github.com/MartinPdeS/BornSim/actions/workflows/tests.yml/badge.svg
513
+ :alt: Test status
514
+ :target: https://github.com/MartinPdeS/BornSim/actions/workflows/tests.yml
515
+ .. |quality| image:: https://github.com/MartinPdeS/BornSim/actions/workflows/quality.yml/badge.svg
516
+ :alt: Static quality check status
517
+ :target: https://github.com/MartinPdeS/BornSim/actions/workflows/quality.yml
518
+ .. |coverage| image:: https://raw.githubusercontent.com/MartinPdeS/BornSim/python-coverage-comment-action-data/badge.svg
519
+ :alt: Test coverage
520
+ :target: https://github.com/MartinPdeS/BornSim/actions/workflows/deploy_coverage.yml
521
+ .. |release| image:: https://img.shields.io/github/v/tag/MartinPdeS/BornSim.svg
522
+ :alt: Latest release tag
523
+ :target: https://github.com/MartinPdeS/BornSim/tags
524
+ .. |publication| image:: https://github.com/MartinPdeS/BornSim/actions/workflows/deploy_release.yml/badge.svg
525
+ :alt: Package publication status
526
+ :target: https://github.com/MartinPdeS/BornSim/actions/workflows/deploy_release.yml
527
+ .. |license| image:: https://img.shields.io/github/license/MartinPdeS/BornSim.svg
528
+ :alt: MIT license
529
+ :target: https://github.com/MartinPdeS/BornSim/blob/master/LICENSE