Skip to main content
BornSim logo

Badge

Status

Python versions

Python 3.11 or newer

Documentation

Documentation build status

Continuous integration

Test status

Static quality checks

Static quality check status

Test coverage

Test coverage

Latest release tag

Latest release tag

Package publication

Package publication status

License

MIT license

BornSim

A Python package for vector Born-series scattering from continuous, isotropic refractive-index fluctuations, such as idealized tissue models.

Install

Requires Python 3.11 or newer.

python -m venv .venv
.venv/bin/python -m pip install -e .

BornSim constructor, function, and method arguments are keyword-only, except add_structures(*structures), which takes positional shape objects. Calls with multiple arguments use one argument per line and a trailing comma.

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.

import matplotlib.pyplot as plt
from bornsim import EnsembleSampling, Grid, Source, Solver
from bornsim.medium.random_medium import GaussianMedium
from bornsim.units import ureg

grid = Grid(
    shape=(8, 8, 8),
    spacing=50 * ureg.nanometer,
)

medium = GaussianMedium(
    background_refractive_index=1.33,
    refractive_index_std=0.01,
    correlation_length=100 * ureg.nanometer,
)

source_configuration_1 = Source(wavelength=633 * ureg.nanometer)

solver = Solver(
    source=source_configuration_1,
    order=3,
)

ensemble_sampling_configuration_1 = EnsembleSampling(
    realizations=4,
    seed=42,
)

result = solver.ensemble(
    medium=medium,
    grid=grid,
    ensemble_sampling=ensemble_sampling_configuration_1,
)

print(f"mu_s = {result.mu_s.to('1 / millimeter')}, g = {result.g}, mu_s_prime = {result.mu_s_prime.to('1 / millimeter')}")

result.plot()

plt.show()

Source describes the supported unpolarized plane wave propagating along +z. GaussianMedium defines numerical random-field statistics. Grid defines spatial shape and spacing once, and is shared by medium.to_volume(grid=grid) and solver.ensemble(grid=grid, ...). Every Volume retains its grid. AngularSampling groups output polar angles, polar integration nodes and azimuth resolution; pass it as Solver(sampling=sampling, ...) to share settings across calculations.

Solver.solve(target=volume) computes full directional scattering and solid-angle integrals by default, providing normalized 3D phase functions for one fixed sample. Use solve_cut with explicit angles for an x-z angular cut, or explicit unit directions for arbitrary observations. Neither cut supplies integrated coefficients. Angle quantities may use degrees or radians. Solver.ensemble(medium=medium, grid=grid, ensemble_sampling=...) averages independent seeded random volumes and returns standard errors. It accepts random media and structures with a random background. Deterministic structures generate a volume and use solve directly.

These calculations return a Result whose differential array has shape (order, polar angle, azimuth) for full calculations. Its plot() returns a Matplotlib figure, with one-standard-error bars for ensembles; plot(terms=True) shows isolated numerical terms without interference or error bars. Call plt.show() to display the figures or figure.savefig("scattering.svg") to export a figure. Full-volume and ensemble coefficients describe finite samples and remain distinct from infinite-medium analytical ones. Undefined anisotropy is NaN in this object API. Existing functions and their return formats remain available.

result.phase_function normalizes every sampled direction using its full solid-angle integral. Full differential data have shape (order, polar angle, azimuth). Curves select one sampled meridian; result.azimuth_average() explicitly requests averaged intensities. result.plot_phase_function(view="3d") preserves directional asymmetry. For notebook rotation, install the notebook extra and use %matplotlib widget. result.amplitudes retains complex amplitudes with the same observation axes followed by polarization and Cartesian axes. Use solver.solve_cut for unnormalized angular cuts, and result.plot_cross_section() for finite-sample area per steradian without passing the original Volume. Saved results retain the physical sample volume.

result.plot_field_norms() shows numerical field terms per realization. See docs/examples/results/result_plots.py for direct plotting examples in the Sphinx Gallery documentation.

Results validate array shapes and physical ranges at construction. result.provenance records calculation settings, versions, seeds, grid and quadrature information. result.save(path="scattering.npz") and Result.load(path="scattering.npz") preserve units, complex amplitudes, uncertainty, and metadata without pickle. Input voxel fields are not stored; retain manually supplied fields separately. See the numerical validation gallery for independent refinement and sampling checks.

Units

bornsim.units exposes TypedUnit’s shared ureg, Quantity, Length, Angle, Dimensionless, and RefractiveIndex, using the same registry as PyMieSim. Physical inputs accept compatible units and reject incompatible dimensions. Lengths and angles require explicit units. Refractive indices, their standard deviations and fluctuation arrays require plain numbers without units, including when passed to Material or shape constructors. Physical configuration has no arbitrary defaults. Supply wavelengths, refractive indices, fluctuation statistics, covariance choice, grid shape and voxel spacing explicitly. StructuredMedium() starts as an empty builder; call add_background(...) before generating a volume. Numerical controls retain defaults, and zero centres and identity shape rotations define the neutral coordinate conventions.

Physical settings and coordinates retain quantities in the supplied units. Validation checks units and scalar shape without converting them. FFT and Fourier kernels extract explicit SI magnitudes where numeric arrays are needed.

Source.wavelength and every numerical array in Result are quantities. Angles, amplitudes, scattering coefficients and uncertainties retain physical units. Saved archives use radians, metres and inverse metres explicitly. Anisotropy, direction vectors, and relative field norms are dimensionless. Use .to("unit") to convert and .magnitude to retrieve an array. Plots convert to degrees and SI scattering units explicitly, including uncertainty bars.

medium.to_volume(grid=grid) generates unit-bearing voxel samples. The low-level Born and ensemble kernels document their numeric SI outputs.

Physical model

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.

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 the angular sampling quadrature to check convergence for strongly forward-peaked scattering.

BornSim includes repeated interactions through the chosen Born order. Infinite-medium first-order formulas live only in the test references. No slab-transmission observable or particle-packing generator is provided; 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.

Scientific references:

Check

.venv/bin/python -m pytest tests

Tests cover the analytic short-correlation limit, contrast scaling, independent angular integration, and input validation.

Numerical media and geometry

GaussianMedium, ExponentialMedium, and WhittleMaternMedium generate Gaussian random refractive index fields with distinct spatial covariance models. Import them from bornsim.medium.random_medium. Choose the covariance by choosing the class; there is no covariance-string constructor argument. Whittle–Matérn requires explicit smoothness, with the convention x = sqrt(2 * nu) * r / correlation_length. Smoothness 0.5 reproduces exponential covariance at the same length parameter.

RandomSphereMedium in bornsim.medium.random_spheres generates a seeded collection of identical nonoverlapping spheres fully contained in the grid box. Supply the sphere count, radius, and both refractive indices. Sequential rejection sampling raises if it cannot place every sphere within its attempt limit. All concrete media implement to_volume(grid=..., seed=...); Medium is abstract.

StructuredMedium voxelizes ordered Layer, Sphere, Ellipsoid, Box, and Cylinder regions. Regions specify absolute indices; later regions replace earlier ones in overlaps. Layers are finite slabs clipped to the box, with a uniform background outside the sample.

from bornsim.units import ureg
from bornsim import Grid, Layer, Sphere, StructuredMedium
from bornsim.medium.random_medium import WhittleMaternMedium
from bornsim import Solver, Source

random_medium_configuration_1 = WhittleMaternMedium(
    smoothness=1.5,
    background_refractive_index=1.33,
    refractive_index_std=0.01,
    correlation_length=1e-07 * ureg.meter,
)

grid_configuration_1 = Grid(
    shape=(12, 12, 12),
    spacing=3e-08 * ureg.meter,
)

random_sample = random_medium_configuration_1.to_volume(
    grid=grid_configuration_1,
    seed=42,
)

structure = StructuredMedium()

structure.add_background(refractive_index=1.33)

layer = Layer(
    lower=-1.8e-07 * ureg.meter,
    upper=0 * ureg.meter,
    refractive_index=1.34,
)

sphere = Sphere(
    radius=7e-08 * ureg.meter,
    refractive_index=1.345,
    centre=(0, 0, 4e-08) * ureg.meter,
)

structure.add_structures(layer, sphere)

grid_configuration_2 = Grid(
    shape=(12, 12, 12),
    spacing=3e-08 * ureg.meter,
)

structured_sample = structure.to_volume(grid=grid_configuration_2)

source_configuration_1 = Source(wavelength=6.33e-07 * ureg.meter)

solver = Solver(
    source=source_configuration_1,
    order=3,
)

result = solver.solve(target=structured_sample)

Solver delegates numerical propagation to BornSeries, which reuses its Green operator across compatible volumes. Ensemble calculations build one engine for all realizations. Voxel fields, random generation, Green propagation, Born iteration, and ensemble statistics live in separate modules.

The numerical method uses a vector volume integral on cubic voxels, rather than finite elements. The documentation’s numerical theory section derives field synthesis, geometry masks, Born orders, the Green self cell, and scattering normalization.

Three-dimensional media

Volume.plot_3d() defaults to an interactive Plotly volume. result.plot_phase_function(view="3d") defaults to a Plotly surface. Select backend="matplotlib" explicitly for static 3D figures, orthogonal slices, or sampled voxel geometry such as spheres.

from bornsim.units import ureg
from bornsim import Grid
from bornsim.medium.random_medium import WhittleMaternMedium

medium = WhittleMaternMedium(
    background_refractive_index=1.33,
    refractive_index_std=0.01,
    correlation_length=1e-07 * ureg.meter,
    smoothness=1.5,
)

grid_configuration_1 = Grid(
    shape=(16, 16, 16),
    spacing=2.5e-08 * ureg.meter,
)

volume = medium.to_volume(
    grid=grid_configuration_1,
    seed=42,
)

figure = volume.plot_3d(
    field='refractive_index',
    opacity_scale='increasing',
    length_unit='nanometer',
)

figure.show()

Matplotlib figures rotate with an interactive backend and can be exported with figure.savefig(). The documentation gallery embeds interactive 3D volumes and phase surfaces. Plotly is included with BornSim and is the default for 3D views. Call figure.show() for browser interaction or figure.write_html() to export an interactive view. Angular and polar result plots use Matplotlib. See the medium visualization documentation for units and rendering conventions.

Numerical Born series

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.

from bornsim.units import ureg
import numpy as np
from bornsim import Directions, EnsembleSampling, Grid
from bornsim.medium.random_medium import GaussianMedium
from bornsim.series import BornSeries
from bornsim.ensemble import ensemble_scattering

medium = GaussianMedium(
    refractive_index_std=0.01,
    correlation_length=1e-07 * ureg.meter,
    background_refractive_index=1.33,
)

grid_configuration_1 = Grid(
    shape=(12, 12, 12),
    spacing=5e-08 * ureg.meter,
)

volume = medium.to_volume(
    grid=grid_configuration_1,
    seed=42,
)

directions = Directions(vectors=[[0.0, 0.0, 1.0], [1.0, 0.0, 0.0]])

engine = BornSeries(
    grid=volume.grid,
    background_refractive_index=volume.background_refractive_index,
    wavelength=6.33e-07 * ureg.meter,
    directions=directions,
    order=3,
)

result = engine.solve(volume=volume)

ensemble_sampling_configuration_1 = EnsembleSampling(
    realizations=4,
    seed=42,
)

grid_configuration_2 = Grid(
    shape=(12, 12, 12),
    spacing=5e-08 * ureg.meter,
)

ensemble = ensemble_scattering(
    medium=medium,
    wavelength=6.33e-07 * ureg.meter,
    order=3,
    ensemble_sampling=ensemble_sampling_configuration_1,
    grid=grid_configuration_2,
)

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.

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.

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.

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.

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.

Higher-order references:

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.

Repository layout

bornsim/             Public API, numerical solvers, Matplotlib plots
tests/analytical/    First-order analytical regression tests
tests/numerical/     Independent Born-series and random-field checks
tests/packaging/     Metadata and release-tool tests
docs/source/         Sphinx documentation
docs/examples/       Runnable examples
development/         Exploratory work excluded from distributions
tools/               Release and wheel-verification commands
conda.recipe/        Conda package recipe
.github/workflows/   Quality, tests, documentation and publication checks

Development

make editable PYTHON=.venv/bin/python
make check PYTHON=.venv/bin/python
make docs PYTHON=.venv/bin/python
make package-check PYTHON=.venv/bin/python
make release-check PYTHON=.venv/bin/python

See CONTRIBUTING.md for development and release instructions. The package uses the MIT license in LICENSE; software citation metadata is provided in CITATION.cff.

API configuration and data ownership

Use Grid, AngularSampling and EnsembleSampling to define spatial, angular and realization settings. Configuration objects are required in place of individual compatibility keywords. EnsembleSampling accepts consecutive seeds or an explicit list of distinct seeds. Material objects share a real absolute refractive index across shapes. Results and their arrays are read-only; StructuredMedium remains mutable. result.angular owns the directional data and is reused on every access. result.meridian(azimuth=90 * ureg.degree) selects sampled directions using physical angles. Exact selection is the default; nearest selection must be requested explicitly and never interpolates or averages intensities.

Theory and simulated figures

The documentation’s theory section derives the vector volume integral, Born orders, coherent amplitudes, phase normalization and ensemble uncertainty. Simulated figures connect these equations to random-field covariance, translation invariance, two-particle interference and voxel refinement. The figures link to runnable examples and state their numerical limitations.

Metadata

Release files for BornSim 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for BornSim 0.5.0
File Size Uploaded
bornsim-0.5.0.tar.gz 1.2 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for BornSim 0.5.0
File Interpreter ABI Platform
bornsim-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.2 MB

Release files / bornsim-0.5.0.tar.gz

Download URL bornsim-0.5.0.tar.gz
Size 1.2 MB
Tags Source
SHA-256 checksum
How to use checksums
f382a50341f136e007290dc59f88bf573aae0eeb3abe54ed74780cd6db0f0b83
BLAKE2b-256 checksum
How to use checksums
0ecce2816d01d35bbf66e9c2c412f5052c5451f39036a8c8ab8df70c9bbf094a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / bornsim-0.5.0-py3-none-any.whl

Download URL bornsim-0.5.0-py3-none-any.whl
Size 68.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
550fbb81ca4f3e42a6dfe1aadd8eadc178055b5be9a54d97505c6ccd4278f300
BLAKE2b-256 checksum
How to use checksums
a1c533dbb90cc37d4d0f97b6402b9f4e16bacac7d3434d6bd8109c24bde88448
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

0.5.1

2 release files

This release

0.5.0 This release

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.8

2 release files

0.2.7

2 release files

0.2.6

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page