Skip to content

Results

After solving a GOAD problem, access the scattering results through the results property of the MultiProblem object. For information about checking the validity of the results, see Checks.

Basic Usage

from goad import MultiProblem, Settings

# Solve the problem and access results
mp = MultiProblem(Settings(geom_path="path/to/geometry.obj"))
mp.solve()
results = mp.results

GOAD organizes scattering results into zones. Properties like results.mueller, results.bins, and results.scat_cross are convenience accessors that return data from the full zone. For zone-specific access (e.g., backscatter parameters), see the Zones section.

Mueller Matrices

The Mueller matrix describes the transformation of the Stokes vector during scattering. GOAD provides Mueller matrices in different forms and for different scattering components.

2D Mueller Matrix

Full angular distribution over theta and phi:

from goad import MultiProblem, Settings

mp = MultiProblem(Settings(geom_path="path/to/geometry.obj"))
mp.solve()

# Get the 2D Mueller matrix
mueller = mp.results.mueller
print(f"Number of bins: {len(mueller)}")
print(f"Mueller matrix elements per bin: {len(mueller[0])}")  # 16 elements

The Mueller matrix is returned as a list of 16-element lists, where each element corresponds to [s11, s12, s13, s14, s21, s22, s23, s24, s31, s32, s33, s34, s41, s42, s43, s44] for each bin.

1D Mueller Matrix

Phi-integrated Mueller matrix (theta only):

from goad import MultiProblem, Settings

mp = MultiProblem(Settings(geom_path="path/to/geometry.obj"))
mp.solve()

# Get the 1D phi-integrated Mueller matrix
mueller_1d = mp.results.mueller_1d
print(f"Number of theta bins: {len(mueller_1d)}")

The 1D Mueller matrix integrates over all phi angles at each theta, providing an azimuthally-averaged scattering pattern.

Mueller Matrix Components

GOAD separates scattering into beam and external diffraction components:

from goad import MultiProblem, Settings

mp = MultiProblem(Settings(geom_path="path/to/geometry.obj"))
mp.solve()

# Access different scattering components
mueller_total = mp.results.mueller  # Total scattering
mueller_beam = mp.results.mueller_beam  # Beam component
mueller_ext = mp.results.mueller_ext  # External diffraction

# Same for 1D Mueller matrices
mueller_1d_total = mp.results.mueller_1d
mueller_1d_beam = mp.results.mueller_1d_beam
mueller_1d_ext = mp.results.mueller_1d_ext
  • Beam component: Direct scattering from ray tracing
  • External diffraction: Diffraction around the particle exterior
  • Total: Sum of beam and external diffraction components. If coherence is disabled, the total is just the linear sum of beam and external diffraction components.

Angular Bins

Access the angular coordinates for each bin:

from goad import MultiProblem, Settings

mp = MultiProblem(Settings(geom_path="path/to/geometry.obj"))
mp.solve()

# Get 2D bins (theta, phi pairs)
bins_2d = mp.results.bins
for theta, phi in bins_2d[:5]:  # First 5 bins
    print(f"Theta: {theta}°, Phi: {phi}°")

# Get 1D bins (theta only)
bins_1d = mp.results.bins_1d
if bins_1d:
    print(f"Theta values: {bins_1d}")

The bins correspond to the center values of each angular bin in the scattering calculation. It is also possible to directly access the theta and phi values from the binning scheme, which is useful if you need to access the bins without running the simulation.

Integrated Parameters

GOAD computes several integrated optical parameters from the Mueller matrix. These parameters are available directly on the results object for convenience, and are also accessible through zone-specific params dictionaries (see Zones for more details).

Scattering Cross Section

The total scattering cross section:

from goad import MultiProblem, Settings

mp = MultiProblem(Settings(geom_path="path/to/geometry.obj"))
mp.solve()

scat_cross = mp.results.scat_cross
print(f"Scattering cross section: {scat_cross}")

Extinction Cross Section

The total extinction cross section (scattering + absorption):

from goad import MultiProblem, Settings

mp = MultiProblem(Settings(geom_path="path/to/geometry.obj"))
mp.solve()

ext_cross = mp.results.ext_cross
print(f"Extinction cross section: {ext_cross}")

Asymmetry Parameter

The asymmetry parameter g (average cosine of scattering angle):

from goad import MultiProblem, Settings

mp = MultiProblem(Settings(geom_path="path/to/geometry.obj"))
mp.solve()

g = mp.results.asymmetry
print(f"Asymmetry parameter: {g}")

Values range from -1 (complete backscattering) to +1 (complete forward scattering).

Single Scattering Albedo

The ratio of scattering to extinction:

from goad import MultiProblem, Settings

mp = MultiProblem(Settings(geom_path="path/to/geometry.obj"))
mp.solve()

albedo = mp.results.albedo
print(f"Single scattering albedo: {albedo}")

Values range from 0 (pure absorption) to 1 (pure scattering).

Zones

GOAD organizes scattering results into zones. For configuration details, see the Zones section in Settings.

Accessing Zones

Access individual zones from the results:

from goad import MultiProblem, Settings

mp = MultiProblem(Settings(geom_path="path/to/geometry.obj"))
mp.solve()

# Access all zones
zones = mp.results.zones
print(f"Available zones: {zones}")

# Access specific zones by type
full_zone = mp.results.full_zone
forward_zone = mp.results.forward_zone
backward_zone = mp.results.backward_zone

print(f"Full zone: {full_zone.name}, bins: {full_zone.num_bins}")
print(f"Forward zone: {forward_zone.name}, bins: {forward_zone.num_bins}")
print(f"Backward zone: {backward_zone.name}, bins: {backward_zone.num_bins}")

Zone-Specific Parameters

Each zone type provides different parameters:

from goad import MultiProblem, Settings

mp = MultiProblem(Settings(geom_path="path/to/geometry.obj"))
mp.solve()

# Full zone parameters (requires full theta coverage)
full_zone = mp.results.full_zone
full_params = full_zone.params
print(f"Scattering cross section: {full_params['scatt_cross']}")
print(f"Extinction cross section: {full_params['ext_cross']}")
print(f"Asymmetry parameter: {full_params['asymmetry']}")
print(f"Single scattering albedo: {full_params['albedo']}")

# Backward zone parameters (lidar-relevant)
backward_zone = mp.results.backward_zone
back_params = backward_zone.params
print(f"Backscatter cross section: {back_params['backscatter_cross']}")
print(f"Lidar ratio: {back_params['lidar_ratio']}")
print(f"Depolarization ratio: {back_params['depolarization_ratio']}")

# Forward zone parameters (optical theorem)
forward_zone = mp.results.forward_zone
fwd_params = forward_zone.params
print(f"Extinction (optical theorem): {fwd_params['ext_cross_optical_theorem']}")

Full zone (requires full theta range 0-180°):

  • scatt_cross: Scattering cross section
  • ext_cross: Extinction cross section
  • asymmetry: Asymmetry parameter g
  • albedo: Single scattering albedo

Backward zone (θ = 180°):

  • backscatter_cross: Backscattering cross section
  • lidar_ratio: Extinction-to-backscatter ratio
  • depolarization_ratio: Linear depolarization ratio

Forward zone (θ ≈ 0°):

  • ext_cross_optical_theorem: Extinction cross section via optical theorem

Zone Mueller Matrices

Each zone contains its own Mueller matrix data:

from goad import MultiProblem, Settings

mp = MultiProblem(Settings(geom_path="path/to/geometry.obj"))
mp.solve()

# Access Mueller matrix for a specific zone
zone = mp.results.full_zone
mueller = zone.mueller  # 2D Mueller matrix for this zone
mueller_1d = zone.mueller_1d  # 1D phi-integrated Mueller matrix
bins = zone.bins  # Angular bins for this zone
bins_1d = zone.bins_1d  # 1D theta bins

print(f"Zone '{zone.name}' has {zone.num_bins} bins")
print(f"Mueller matrix shape: {mueller.shape}")

Power Budget

Track energy conservation throughout the simulation:

from goad import MultiProblem, Settings

mp = MultiProblem(Settings(geom_path="path/to/geometry.obj"))
mp.solve()

powers = mp.results.powers
print(f"Input power: {powers['input']}")
print(f"Output power: {powers['output']}")
print(f"Absorbed power: {powers['absorbed']}")
print(f"Missing power: {powers['missing']}")

# Check energy conservation
total_accounted = (
    powers["output"]
    + powers["absorbed"]
    + powers["trnc_ref"]
    + powers["trnc_rec"]
    + powers["trnc_clip"]
    + powers["trnc_energy"]
    + powers["trnc_area"]
)
print(f"Energy conservation error: {powers['input'] - total_accounted}")

The power dictionary contains:

  • input: Incident beam power ie. the mean geometrical cross section
  • output: Total near-field output power, the sum of scattering and absorption
  • absorbed: Power absorbed by the particle
  • trnc_ref: Power lost due to max total internal reflections reached
  • trnc_rec: Power lost due to max recursions reached
  • trnc_clip: Power lost during beam clipping
  • trnc_energy: Power lost due to minimum energy cutoff
  • trnc_area: Power lost due to minimum area cutoff
  • trnc_cop: Power lost due to total cutoff power threshold
  • clip_err: Error from clipping algorithm
  • ext_diff: External diffraction power
  • missing: Total unaccounted power

Complete Example

import numpy as np
from goad import BinningScheme, MultiProblem, Orientation, Settings, ZoneConfig

# Configure and solve
settings = Settings(
    geom_path="path/to/geometry.obj",
    wavelength=0.532,
    orientation=Orientation.uniform(num_orients=100),
    zones=[ZoneConfig(BinningScheme.simple(num_theta=180, num_phi=48))],
)
mp = MultiProblem(settings)
mp.solve()

# Access all results
results = mp.results

# Extract scattering phase function (S11 element)
mueller_1d = np.array(results.mueller_1d)
s11 = mueller_1d[:, 0]  # First column is S11
theta = np.array(results.bins_1d)

# Print integrated parameters
print(f"Scattering cross section: {results.scat_cross:.6f}")
print(f"Extinction cross section: {results.ext_cross:.6f}")
print(f"Asymmetry parameter: {results.asymmetry:.4f}")
print(f"Single scattering albedo: {results.albedo:.4f}")

# Access zone-specific parameters
backward = results.backward_zone
print(f"Lidar ratio: {backward.params['lidar_ratio']:.4f}")
print(f"Backscatter cross section: {backward.params['backscatter_cross']:.6f}")

# Check power budget
powers = results.powers
efficiency = powers["output"] / powers["input"]
print(f"Scattering efficiency: {efficiency:.4f}")
print(f"Missing power fraction: {powers['missing'] / powers['input']:.2e}")

Result Properties Reference

Property Type Description
bins ndarray 2D angular bins [[theta, phi], ...]
bins_1d ndarray 1D theta bins
mueller ndarray 2D Mueller matrix (total)
mueller_beam ndarray 2D Mueller matrix (beam component)
mueller_ext ndarray 2D Mueller matrix (external diffraction)
mueller_1d ndarray 1D Mueller matrix (total)
mueller_1d_beam ndarray 1D Mueller matrix (beam component)
mueller_1d_ext ndarray 1D Mueller matrix (external diffraction)
scat_cross float Scattering cross section
ext_cross float Extinction cross section
asymmetry float Asymmetry parameter g
albedo float Single scattering albedo
powers dict[str, float] Power budget dictionary
zones Zones Collection of all zones
full_zone Zone Full scattering zone (0-180°)
forward_zone Zone Forward scattering zone (θ ≈ 0°)
backward_zone Zone Backward scattering zone (θ = 180°)

Zone Properties Reference

Property Type Description
name str Zone name (e.g., "full", "forward", "backward")
zone_type ZoneType Zone type enum (Full, Forward, Backward, Custom)
num_bins int Number of angular bins in this zone
bins ndarray 2D angular bins for this zone
bins_1d ndarray 1D theta bins for this zone
mueller ndarray 2D Mueller matrix for this zone
mueller_1d ndarray 1D Mueller matrix for this zone
params dict[str, float] Zone-specific integrated parameters
label str \| None Optional user-defined label