Settings
The Settings object configures a GOAD simulation. It controls physical parameters, numerical methods, and output options.
Basic Usage
At a minimum, you must specify the path to a geometry file or directory containing geometry files and give a refractive index:
from goad import Geom, MultiProblem, Settings
# Basic settings with minimal configuration
REFR_INDEX = 1.31 + 0j
geoms = Geom.from_file("path/to/geometry.obj", [REFR_INDEX])
settings = Settings()
mp = MultiProblem(settings, geoms)
mp.solve()
By default, the refractive index is assigned to all geometries and their constituent shapes. For advanced use, see Containment Tree.
Geometry
The geometry defines the units of the problem. If your geometry file is in microns, then you should also specify the wavelength in microns. All faces in the geometry must be planar and have some non-zero area. GOAD will return with an error if there are faces with zero area (ie. extremely thin triangles), since it needs to compute normals of each face by a cross product of 2 non-colinear edge vectors. You can make geometries in the open-source Blender software, or use some example geometries straight from Python here.
If you specify a directory, GOAD will attempt to load all files with the .obj extension in the directory. It will then choose geometries at random for each orientation in the simulation. See Orientation Distribution for more details.
Internally, the geometry (or list of geometries, if a directory was specified) holds a list of Shape objects. For simple particles, like a cube, there is just a single Shape object. If the scattering geometry is made up of multiple surfaces, then there will be one Shape for each surface. For example, a cube within a cube yields a single geometry with 2 constituent cube shapes.
Containment Tree
It is possible to show the hierarchy of shapes in a geometry by printing its containment graph:
from goad import Geom # noqa: E402
geom = Geom.from_file("path/to/multi_shape.obj", [1.31 + 0j])[0]
print(geom.containment_tree())
Output:
medium : 1.0000 + 0.0000i
├── shape 0 : 1.3100 + 0.0000i
│ ├── shape 1 : 1.3100 + 0.0000i
│ │ └── shape 3 : 1.3100 + 0.0000i
│ │ └── shape 5 : 1.3100 + 0.0000i
│ └── shape 2 : 1.3100 + 0.0000i
└── shape 4 : 1.3100 + 0.0000i
The top line shows the surrounding medium and each subsequent line shows a shape, indented by its containment depth. Sibling shapes share a parent.
When running computations on particles with multiple layers, particles with embeddings, or other so-called poly-particle cases, you will probably need to set the refractive index of each shape directly (otherwise all constituents will have the same refractive index). Use geom.refr_index(idx) to read and geom.set_refr_index(idx, n) to write, where n is a Python complex:
from goad import Geom # noqa: E402
geom = Geom.from_file("path/to/multi_shape.obj", [1.31 + 0j])[0]
# Override the refractive index of individual shapes
geom.set_refr_index(0, 1.5 + 0.0j) # Set shape 0 to 1.5
geom.set_refr_index(1, 1.33 + 0.01j) # Set shape 1 to 1.33 + 0.01j
print(geom.containment_tree())
Output:
medium : 1.0000 + 0.0000i
├── shape 0 : 1.5000 + 0.0000i
│ ├── shape 1 : 1.3300 + 0.0100i
│ │ └── shape 3 : 1.3100 + 0.0000i
│ │ └── shape 5 : 1.3100 + 0.0000i
│ └── shape 2 : 1.3100 + 0.0000i
└── shape 4 : 1.3100 + 0.0000i
Physical Parameters
Wavelength
The wavelength of incident light in micrometers:
from goad import Geom, MultiProblem, Settings # noqa: E402
# Configure wavelength (in micrometers)
geoms = Geom.from_file("path/to/geometry.obj", [1.31 + 0j])
settings = Settings(
wavelength=0.532, # 532 nm
)
mp = MultiProblem(settings, geoms)
mp.solve()
Default: 0.532 (532 nm, green laser)
Refractive Indices
The particle refractive index is supplied per-shape when the geometry is loaded (Geom.from_file(path, [n1, n2, ...])). A single complex value is broadcast to every shape; otherwise pass one entry per shape. The medium refractive index is set on Settings:
from goad import Geom, MultiProblem, Settings # noqa: E402
# Configure refractive indices for particle and medium
geoms = Geom.from_file("path/to/geometry.obj", [1.5 + 0.01j])
settings = Settings(
medium_refr_index=1.33 + 0.0j,
)
mp = MultiProblem(settings, geoms)
mp.solve()
Defaults:
- Particle refractive index (broadcast):
1.31 + 0j(typical glass) medium_refr_index:1.0 + 0j(vacuum/air)
See Containment Tree for inspecting and overriding per-shape refractive indices on a loaded Geom.
Particle Scaling
Scales the entire problem, including geometry and wavelength. Does not change the physics, only used for improving clipping algorithm accuracy. The default value is usually sufficient.
Parameter: scale
Default: 1.0
Orientation Distribution
Define how particle orientations are sampled:
from goad import ( # noqa: E402
EulerConvention,
Geom,
MultiProblem,
Orientation,
Settings,
)
# Configure particle orientation distribution
geoms = Geom.from_file("path/to/geometry.obj", [1.31 + 0j])
settings = Settings(
orientation=Orientation.uniform(
num_orients=100, euler_convention=EulerConvention("ZYZ")
),
)
mp = MultiProblem(settings, geoms)
mp.solve()
Default: Orientation.uniform(num_orients=1) with EulerConvention('ZYZ')
For discrete orientations:
from goad import ( # noqa: E402
Euler,
EulerConvention,
Geom,
MultiProblem,
Orientation,
Settings,
)
# Configure discrete orientations
orients = Orientation.discrete(
eulers=[Euler(0, 0, 0), Euler(45, 90, 0)], euler_convention=EulerConvention("ZYZ")
)
geoms = Geom.from_file("path/to/geometry.obj", [1.31 + 0j])
settings = Settings(orientation=orients)
mp = MultiProblem(settings, geoms)
mp.solve()
The output will be an average over the orientations. Results from individual orientations are not stored. See Results for more details.
Seed
Seeds the random number generator for reproducibility.
Parameter: seed
Default: None
Zones
Zones define a set of query points to evaluate the far-field scattering at. By default, GOAD creates three zones:
- Full zone - The full scattering sphere from θ=0° to θ=180°. Used for computing integrated parameters like asymmetry, scattering cross-section, and albedo.
- Forward zone - A single point at θ=0.01° (slightly off-axis for numerical stability). Used for computing extinction cross-section via the optical theorem.
- Backward zone - A single point at θ=180°. Used for computing backscatter cross-section, lidar ratio, and depolarization ratio.
You can add additional zones as needed for your application. For backscattering-only applications, you can exclude the full zone and compute only at forward and backward scattering for faster computations.
Note: GOAD automatically determines the zone type based on the theta range of your binning scheme. If the theta range covers 0° to 180°, it is classified as a Full zone and integrated parameters (asymmetry, scattering cross-section, etc.) will be computed. If the range is partial, it is classified as a Custom zone and these parameters will not be computed.
Different parameters are computed depending on the zone type - see Integrated Parameters for more details.
Each zone is specified by a binning scheme and an optional label. See Binning Schemes for more info about binning schemes.
from goad import BinningScheme, Geom, MultiProblem, Settings, ZoneConfig # noqa: E402
geoms = Geom.from_file("path/to/geometry.obj", [1.31 + 0j])
# Default: single full zone with interval binning (high-res forward/back)
settings = Settings()
# Custom full zone with simple binning
settings = Settings(
zones=[ZoneConfig(BinningScheme.simple(180, 48))],
)
# Labeled zone
settings = Settings(
zones=[ZoneConfig(BinningScheme.simple(90, 24), label="coarse")],
)
# Backscatter-only (no full zone, just forward + backward)
settings = Settings(zones=[])
mp = MultiProblem(settings, geoms)
mp.solve()
Default: A single full zone with interval binning (high resolution at forward and backward angles).
Binning Schemes
Control the angular resolution of scattering calculation in the far-field. As particle size increases, the width of peaks in the scattering decreases. GOAD currently requires the user to choose a sufficiently fine binning scheme to resolve the peaks. For phi angles, a relatively course binning scheme can be used, but for theta angles, care should be taken to ensure sufficient resolution, otherwise the integrated parameters lose accuracy. The compute time approximately scales with the number of bins, which for simple and interval binning schemes is just the product of the number of bins in each dimension.
Simple Binning
Uniform spacing in theta and phi:
from goad import BinningScheme, Geom, MultiProblem, Settings, ZoneConfig # noqa: E402
# Configure angular binning for scattering output
geoms = Geom.from_file("path/to/geometry.obj", [1.31 + 0j])
settings = Settings(
zones=[ZoneConfig(BinningScheme.simple(num_theta=180, num_phi=48))],
)
mp = MultiProblem(settings, geoms)
mp.solve()
Default: BinningScheme.interval(thetas=[0, 5, 175, 179, 180], theta_spacings=[0.1, 2.0, 0.5, 0.1], phis=[0, 360], phi_spacings=[7.5])
Interval Binning
Variable resolution for different angular regions:
from goad import BinningScheme, Geom, MultiProblem, Settings, ZoneConfig # noqa: E402
# Use variable angular resolution
geoms = Geom.from_file("path/to/geometry.obj", [1.31 + 0j])
settings = Settings(
zones=[
ZoneConfig(
BinningScheme.interval(
thetas=[0, 90, 180],
theta_spacings=[1, 2], # 1° steps up to 90°, then 2° steps
phis=[0, 360],
phi_spacings=[2],
)
)
],
)
mp = MultiProblem(settings, geoms)
mp.solve()
This example uses 1° resolution for forward scattering (0-90°) and 2° for backward scattering (90-180°). interval binning schemes are useful if the user is only interested in a specific angular scattering range, eg 6°-25°.
Custom Binning
Specify arbitrary bin edges:
from goad import BinningScheme, Geom, MultiProblem, Settings, ZoneConfig # noqa: E402
# Specify arbitrary bin edges
binning = BinningScheme.custom(
bins=[
[[0, 10], [0, 360]], # Forward scattering cone
[[10, 170], [0, 360]], # Side scattering
[[170, 180], [0, 360]], # Backscattering cone
]
)
geoms = Geom.from_file("path/to/geometry.obj", [1.31 + 0j])
settings = Settings(zones=[ZoneConfig(binning)])
mp = MultiProblem(settings, geoms)
mp.solve()
GOAD will compute the bin centres automatically. GOAD will not compute the 1D mueller matrix or integrated parameters if using a custom binning scheme.
Mapping Method
Choose how near-field results map to the far-field:
from goad import Geom, Mapping, MultiProblem, Settings # noqa: E402
# Configure near-to-far field mapping method
geoms = Geom.from_file("path/to/geometry.obj", [1.31 + 0j])
settings = Settings(
mapping=Mapping("ad"), # 'ad' for Aperture Diffraction, 'go' for Geometric Optics
)
mp = MultiProblem(settings, geoms)
mp.solve()
Options:
'ad': Aperture Diffraction (default, more accurate). Suitable for fixed-orientation computations.'go': Geometric Optics (faster, suitable for very large particles). Generally not suitable for fixed-orientation computations.
Default: Mapping('ad')
Beam Tracing Parameters
Beams traced in the near-field if they pass the following checks, in order:
- Beam power is below the threshold
- Beam area is below the threshold
- Beam number of recursion is above the threshold
If the beam is from a total internal reflection event, the beam is traced even if the recursion is above the threshold, as long as the total internal reflection count is below the threshold.
Beam Thresholds
Control when beams are truncated during ray tracing:
from goad import Geom, MultiProblem, Settings # noqa: E402
# Configure beam tracing thresholds
geoms = Geom.from_file("path/to/geometry.obj", [1.31 + 0j])
settings = Settings(
beam_power_threshold=1e-6, # Stop tracking beams below this power
beam_area_threshold_fac=1e-3, # Stop tracking beams smaller than this fraction
cutoff=1e-10, # Global energy cutoff
)
mp = MultiProblem(settings, geoms)
mp.solve()
Defaults:
beam_power_threshold:0.005(discard beams below 0.5% of incident power).beam_area_threshold_fac:0.1(factor × λ² determines the physical area threshold, below which beams are discarded. It scales with λ² following the applicability of geometric optics).cutoff:0.99(trace 99% of energy in the near field, then map. You generally want to use a value of at least 0.95 here, unless you have a good reason to do otherwise).
Lower thresholds and higher cutoff increases accuracy but slows computation.
Recursion Limits
Limit internal beams bounces:
from goad import Geom, MultiProblem, Settings # noqa: E402
# Configure ray tracing limits
geoms = Geom.from_file("path/to/geometry.obj", [1.31 + 0j])
settings = Settings(
max_rec=10, # Maximum internal reflections
max_tir=5, # Maximum total internal reflections
)
mp = MultiProblem(settings, geoms)
mp.solve()
Defaults:
max_rec:10(maximum internal reflections)max_tir:10(maximum total internal reflections)
Increase these for complex internal ray paths, but expect slower performance. High numbers of total internal reflections recommended for backscattering computations, ie. 20.
Output Options
Directory
Specify output directory for simulation data:
Parameter: directory
Default: "goad-run"
Coherence
Enable coherent beam addition (phase tracking):
Parameter: coherence
Default: True
Enables coherent beam addition with phase tracking for interference effects. If coherence is enabled, GOAD traces the phase of each beam, and combines amplitude matrices in the far-field with interference. If coherence is disabled, GOAD traces the phase of each beam, but combines the far-field contributions of beams in the far-field only by a linear summation of Mueller matrices. Coherence should be enabled for backscattering computations.
Verbosity
Suppress console output:
Parameter: quiet
Default: False
Set to True to silence progress messages.
Complete Example
from goad import ( # noqa: E402
BinningScheme,
Geom,
Mapping,
MultiProblem,
Orientation,
Settings,
ZoneConfig,
)
# Complete configuration example
geoms = Geom.from_file("path/to/geometry.obj", [1.5 + 0.01j])
settings = Settings(
medium_refr_index=1.0 + 0.0j,
wavelength=0.532,
orientation=Orientation.uniform(num_orients=100),
zones=[ZoneConfig(BinningScheme.simple(num_theta=180, num_phi=48))],
mapping=Mapping("ad"),
beam_power_threshold=1e-6,
beam_area_threshold_fac=1e-3,
cutoff=0.999,
max_rec=10,
max_tir=5,
coherence=False,
quiet=False,
directory="output/",
)
mp = MultiProblem(settings, geoms)
mp.solve()
Parameter Reference
Note: geometry loading is no longer part of Settings. Load geometry with Geom.from_file(path, refr_indices) and pass the result as the second argument to MultiProblem / Convergence.
| Parameter | Type | Default | Description |
|---|---|---|---|
wavelength |
float |
0.532 |
Wavelength in geometry units |
medium_refr_index |
complex |
1.0 + 0j |
Refractive index of the surrounding medium |
orientation |
Orientation |
Orientation.uniform(1) |
Orientation distribution |
zones |
list[ZoneConfig] |
Single full zone with interval binning | Zone configurations for far-field evaluation |
mapping |
Mapping |
Mapping('ad') |
Near-to-far field mapping method |
beam_power_threshold |
float |
0.005 |
Beam power truncation threshold |
beam_area_threshold_fac |
float |
0.1 |
Beam area truncation factor |
cutoff |
float |
0.99 |
Energy tracking cutoff |
max_rec |
int |
10 |
Maximum internal reflections |
max_tir |
int |
10 |
Maximum total internal reflections |
scale |
float |
1.0 |
Geometry scaling factor |
seed |
int |
None |
Seed for random number generator |
directory |
str |
"goad_run" |
Output directory path |
coherence |
bool |
True |
Enable coherent beam addition |
quiet |
bool |
False |
Suppress console output |