iactrace.core

Low-level ray tracing components. Most users should use the Telescope and Camera classes instead of these functions directly.

Render Engine

render_optics generates rays from sources and materialises the full ray buffer; render_optics_accumulate folds an accumulator over primary elements instead, so peak memory does not grow with element count. trace_optics takes caller-supplied rays.

iactrace.core.render_optics(optical_groups, obstruction_groups, sources, values, source_type, *, spectrum=None)[source]

Render sources through the optics; return one flat RayBundle.

Materialises the full (n_elements * n_sources * n_samples,) ray buffer. Use render_optics_accumulate when only a small aggregate (image, response matrix, …) is needed.

spectrum is the source’s Spectrum (default: monochromatic at DEFAULT_WAVELENGTH).

iactrace.core.render_optics_accumulate(optical_groups, obstruction_groups, sources, values, source_type, accumulator, init, *, spectrum=None)[source]

Carry-folding render: walk stage-0 elements with an accumulator.

Calls accumulator(carry, per_element_bundle) -> carry for each primary element instead of stacking outputs. Peak memory is bounded by init plus one element’s rays, regardless of element count.

The per-element bundle has length n_sources * n_samples in world coordinates, source-major (the first n_samples rays belong to sources[0]). spectrum is the source’s Spectrum (default: monochromatic at DEFAULT_WAVELENGTH).

iactrace.core.trace_optics(optical_groups, obstruction_groups, ray_origins, ray_directions, values, record_trajectory=False, *, wavelength=None)[source]

Trace rays from arbitrary origins through full optical system.

Parameters:
  • optical_groups – List of OpticalElementGroup (combined mirrors + lenses).

  • obstruction_groups – List of ObstructionGroup.

  • ray_origins (ndarray (n_rays, 3)) –

    .

  • ray_directions (ndarray (n_rays, 3)) – Normalized.

  • values (ndarray (n_rays,)) –

    .

  • wavelength (ndarray (n_rays,)) – a scalar shared by every ray, a per-ray. array, or a Spectrum, which draws one wavelength per ray. Default DEFAULT_WAVELENGTH.

  • record_trajectory – When True, also collect the per-stage hit points and return them as a Trajectory alongside the RayBundle.

Returns:

  • A TraceResult. Its rays are in 3D

  • space after all optical stages; its trajectory is None unless

  • record_trajectory was set, in which case the

  • Trajectory holds the source point

  • followed by each stage’s landing point (world frame),

  • (n_stages + 1, n_rays, 3). It ends on the last optic.

Handoff to a local frame

iactrace.core.handoff_to_frame(rb, obstruction_groups, position, rotation)[source]

Hand a world-frame bundle off to a local frame: shadow, then reframe.

iactrace.core.apply_final_leg_shadow(rb, obstruction_groups, plane_position, plane_rotation)[source]

Shadow the converging beam on the final last-optic -> focal-plane leg.

iactrace.core.final_leg_points(rb, plane_position, plane_rotation, fallback)[source]

Where the final last-optic -> focal-plane leg lands.

Ray Bundle

iactrace.core.DEFAULT_WAVELENGTH = 400.0

Convert a string or number to a floating-point number, if possible.

class iactrace.core.RayBundle[source]

Bases: Module

Bundle of rays through the optical system.

The frame of origins / directions is implicit and depends on where the bundle came from: Telescope.render and Telescope.trace return rays in the camera-local frame, ready for Camera.collect / Camera.image.

By the time the bundle reaches Camera.collect the entries of values are photoelectrons, not raw photons.

Variables:
  • origins (ndarray (n_rays, 3)) – Ray positions. Meaningful only where alive.

  • directions (ndarray (n_rays, 3)) – Ray directions. Meaningful only where alive.

  • values (ndarray (n_rays,)) – Throughput-weighted intensities.

  • path_length (ndarray (n_rays,)) – Accumulated optical path length, in metres.

  • n (ndarray (n_rays,)) – Refractive index of the medium each ray is in, carried so downstream consumers can weight the final geometric leg.

  • wavelength (ndarray (n_rays,)) – Per-ray wavelength.

  • alive (ndarray (n_rays,)) – Per-ray liveness, all-True at construction.

__init__(origins, directions, values, path_length, n, alive=None, wavelength=None)[source]
origins = <dataclasses._MISSING_TYPE object>
directions = <dataclasses._MISSING_TYPE object>
values = <dataclasses._MISSING_TYPE object>
path_length = <dataclasses._MISSING_TYPE object>
n = <dataclasses._MISSING_TYPE object>
wavelength = <dataclasses._MISSING_TYPE object>
alive = <dataclasses._MISSING_TYPE object>
replace(**changes)[source]

Copy with the given fields replaced; unknown names raise TypeError.

to_frame(origin, rotation)[source]

Transform rays to the frame given by origin + Euler rotation.

class iactrace.core.LazyRayBundle[source]

Bases: Module

A RayBundle described by a render that has not been evaluated yet.

Holds the optics, obstructions, camera frame, and source description needed to evaluate itself. Output is delivered in the local frame defined by camera_position and camera_rotation.

Consume it either with fold – walk per primary-mirror element with an accumulator, so the full (n_elements * n_sources * n_samples,) ray buffer is never materialised – or with materialise, when the per-ray output itself is the result (spot diagrams, Camera.collect).

optical_groups = <dataclasses._MISSING_TYPE object>
obstruction_groups = <dataclasses._MISSING_TYPE object>
camera_position = <dataclasses._MISSING_TYPE object>
camera_rotation = <dataclasses._MISSING_TYPE object>
sources = <dataclasses._MISSING_TYPE object>
source_values = <dataclasses._MISSING_TYPE object>
spectrum = <dataclasses._MISSING_TYPE object>
source_type = <dataclasses._MISSING_TYPE object>
fold(accumulator, init)[source]

Per-element scan: accumulator(carry, rb_local) -> carry.

rb_local is one element’s RayBundle, already handed off into the local frame.

materialise()[source]

Run the render eagerly; return a flat local-frame RayBundle.

__init__(optical_groups, obstruction_groups, camera_position, camera_rotation, sources, source_values, spectrum, source_type)

Trajectories

class iactrace.core.TraceResult[source]

Bases: NamedTuple

A traced RayBundle.

Variables:
rays

Alias for field number 0

trajectory

Alias for field number 1

static __new__(_cls, rays, trajectory=None)

Create new instance of TraceResult(rays, trajectory)

class iactrace.core.Trajectory[source]

Bases: Module

Per-step 3-D positions of a bundle of rays, recorded through a trace.

Variables:

points (jax.Array) – (steps + 1, n_rays, 3) per-step ray positions.

points = <dataclasses._MISSING_TYPE object>
__array__(dtype=None)[source]

Expose the raw (steps + 1, n_rays, 3) points to np.asarray.

__init__(points)

Source Spectra

What a source emits, as a distribution over wavelength.

class iactrace.core.Spectrum[source]

Bases: Module

A source’s distribution over wavelength.

Renders consume a spectrum through sample, which gives each ray one wavelength drawn from the distribution – so a broadband render costs exactly as many rays as a monochromatic one, and the band is integrated by the Monte Carlo ensemble. The draw is reparameterised (inverse-CDF of a fixed uniform stream), so gradients flow to the spectrum’s own parameters.

bins is the quadrature counterpart, for callers who would rather sweep deterministically over (wavelengths, weights).

abstractmethod sample(key, shape)[source]

Draw wavelengths of the given shape from the distribution.

abstractmethod bins()[source]

Return (wavelengths, weights) for a quadrature sweep.

weights are normalised (sum to 1) so a weighted sum of per-wavelength renders estimates the flux-averaged broadband result.

__init__()
class iactrace.core.ConstantSpectrum[source]

Bases: Spectrum

Monochromatic source: every ray at a single wavelength (degenerate case).

Variables:

wavelength (jax.Array) – The single scalar wavelength.

wavelength = <dataclasses._MISSING_TYPE object>
sample(key, shape)[source]

Draw wavelengths of the given shape from the distribution.

bins()[source]

Return (wavelengths, weights) for a quadrature sweep.

weights are normalised (sum to 1) so a weighted sum of per-wavelength renders estimates the flux-averaged broadband result.

__init__(wavelength)
class iactrace.core.TabulatedSpectrum[source]

Bases: Spectrum

Piecewise-linear photon density sampled at a set of wavelengths.

density is the relative spectral photon density at wavelengths (any non-negative scale; it is normalised internally). sample draws from that density by exact inverse-CDF; bins returns the same density as trapezoidal quadrature weights.

Variables:
  • wavelengths (ndarray (K,)) – Sample wavelengths, strictly ascending. At least two are required.

  • density (ndarray (K,)) – Relative photon density aligned with wavelengths, non-negative and not zero everywhere.

Raises:

ValueError – if the two arrays disagree in shape, if fewer than two wavelengths are given, or if the wavelengths are not strictly ascending or the density carries no mass.

wavelengths = <dataclasses._MISSING_TYPE object>
density = <dataclasses._MISSING_TYPE object>
sample(key, shape)[source]

Draw wavelengths from the piecewise-linear density.

Reparameterised so gradients flow to the spectrum’s own parameters.

bins()[source]

Return (wavelengths, weights) for a quadrature sweep.

weights are normalised (sum to 1) so a weighted sum of per-wavelength renders estimates the flux-averaged broadband result.

classmethod from_density(wavelengths, density)[source]

Build from (wavelengths, density) samples (sorted internally).

__init__(wavelengths, density)
iactrace.core.as_spectrum(wavelength)[source]

Coerce a wavelength argument to a Spectrum.

A Spectrum is returned as-is; anything else (a scalar) becomes a ConstantSpectrum.

Refractive Index

A refracting element’s index as a function of wavelength.

class iactrace.core.RefractiveIndex[source]

Bases: Module

A refracting element’s index as a function of wavelength, n(lambda).

Subclasses provide:

  • n_at – per-ray index (n_rays,) at the given wavelengths, selecting per-element parameters with element_idx.

  • reference – a per-element nominal index (N,) at a single design wavelength, used by the focal-length <-> curvature maths (set_focal_lengths).

  • n_elements – the number of elements N the model sizes.

abstractmethod n_at(element_idx, wavelength)[source]
abstractmethod reference(wavelength=400.0)[source]
abstract property n_elements
__init__()
class iactrace.core.ConstantIndex[source]

Bases: RefractiveIndex

Wavelength-independent per-element index (the monochromatic case).

Variables:

values (ndarray (N,)) – Per-element refractive index.

values = <dataclasses._MISSING_TYPE object>
n_at(element_idx, wavelength)[source]
reference(wavelength=400.0)[source]
property n_elements
__init__(values)
class iactrace.core.TabulatedIndex[source]

Bases: RefractiveIndex

Per-element refractive index, linearly interpolated in wavelength.

Variables:
  • wavelengths (ndarray (K,)) – Lookup axis, sorted ascending. Same units as wavelength.

  • n_values (ndarray (N, K)) – Per-element index samples aligned with wavelengths.

wavelengths = <dataclasses._MISSING_TYPE object>
n_values = <dataclasses._MISSING_TYPE object>
n_at(element_idx, wavelength)[source]
reference(wavelength=400.0)[source]
property n_elements
classmethod from_table(wavelengths, n_values, n_elements)[source]

Build from measured n(lambda) samples.

wavelengths (K,) is sorted internally; n_values is (K,) broadcast to all n_elements elements, or (N, K) as-is.

__init__(wavelengths, n_values)
class iactrace.core.SellmeierIndex[source]

Bases: RefractiveIndex

Sellmeier-equation refractive index per element.

n(lambda)^2 = 1 + sum_j b_j * lambda^2 / (lambda^2 - c_j).

The b coefficients are dimensionless; the c coefficients carry units of wavelength squared and must match the unit of wavelength.

Variables:
  • b (ndarray (N, M)) – Per-element Sellmeier B coefficients.

  • c (ndarray (N, M)) – Per-element Sellmeier C coefficients.

b = <dataclasses._MISSING_TYPE object>
c = <dataclasses._MISSING_TYPE object>
n_at(element_idx, wavelength)[source]
reference(wavelength=400.0)[source]
property n_elements
__init__(b, c)
iactrace.core.as_refractive_index(index, n_elements)[source]

Coerce an index argument to a RefractiveIndex sized n_elements.

index may be a model, a scalar, or a per-element (N,) array. Raises ValueError if it is None, or a model sized for a different number of elements.

Optical Element Composition

class iactrace.core.OpticalElementGroup[source]

Bases: Module

Optical element group composing surface + aperture + interaction.

All optical elements (mirrors, lenses, slabs) are instances of this class configured with appropriate modules.

__init__(positions, rotations, surface, aperture, interaction_module, sample_key, optical_stage=0, n_samples=100, bsdf=None)[source]
positions = <dataclasses._MISSING_TYPE object>
rotations = <dataclasses._MISSING_TYPE object>
surface = <dataclasses._MISSING_TYPE object>
aperture = <dataclasses._MISSING_TYPE object>
interaction_module = <dataclasses._MISSING_TYPE object>
optical_stage = <dataclasses._MISSING_TYPE object>
n_samples = <dataclasses._MISSING_TYPE object>
bsdf = <dataclasses._MISSING_TYPE object>
sample_key = <dataclasses._MISSING_TYPE object>
property interaction
property kind

User-facing element kind, derived from the interaction module.

transform_to_world()[source]

Compute geometry from current surface params and transform to world coordinates.

Samples are generated at call time using the stored n_samples and sample_key.

Return type:

Tuple of (points_world, normals_world, weights) arrays.

sample_primary_geometry(roughness_salt)[source]

Sample this group’s aperture, with this group’s surface roughness applied.

Parameters:

roughness_salt – Integer folded into this group’s sample_key to draw the roughness perturbation.

Returns:

  • Tuple of (points_world, normals_world, weights) arrays, as

  • transform_to_world, with normals_world perturbed.

intersect_t(element_idx, origins, directions)[source]

Hit distance only, for the nearest-hit search over a stage.

Parameters:
  • element_idx – Index of the element within this group.

  • origins (ndarray (n_rays, 3)) – Rays in world coordinates.

  • directions (ndarray (n_rays, 3)) – Rays in world coordinates.

Returns:

  • (n_rays,) hit distances, inf where the ray misses the

  • surface or lands outside the element’s aperture.

hit_geometry(element_idx, origins, directions)[source]

World-frame hit point and normal, for a per-ray element index.

The counterpart to intersect_t: once the search knows which element each ray settled on, this evaluates the surface there.

Parameters:
  • element_idx (ndarray (n_rays,)) – Per-ray element index.

  • origins (ndarray (n_rays, 3)) – Rays in world coordinates.

  • directions (ndarray (n_rays, 3)) – Rays in world coordinates.

Return type:

Tuple of (points_world, normals_world), each (n_rays, 3).

perturb_normals(normals, roughness_salt, element_idx=None)[source]

Apply this group’s own BSDF surface-roughness perturbation.

roughness_salt is folded into this group’s sample_key.

apply_interaction(directions, normals, points, element_idx, current_n, wavelength=None)[source]

Apply this group’s physical interaction (reflect/refract/slab) at a hit.

See Interaction.apply for the return value.

interact(directions, normals, points, element_idx, current_n, roughness_salt, wavelength=None)[source]

Perturb normals for roughness, then apply the physical interaction.

See Interaction.apply for the return value.

Apertures

class iactrace.core.Aperture[source]

Bases: Module

Abstract base for aperture modules.

abstractmethod check(x, y, element_idx)[source]
abstractmethod sample(key, n_samples)[source]
abstractmethod get_area_data()[source]
abstractmethod area_fn(data)[source]
__init__()
class iactrace.core.DiskAperture[source]

Bases: Aperture

Circular or annular aperture defined by outer and inner radii.

Supports solid disks (inner_radii=0) and annular rings.

Variables:
  • radii (ndarray (N,)) – Outer radius per element.

  • inner_radii (ndarray (N,)) – Inner radius per element, 0 for a solid disk.

radii = <dataclasses._MISSING_TYPE object>
inner_radii = <dataclasses._MISSING_TYPE object>
check(x, y, element_idx)[source]
sample(key, n_samples)[source]

(N, n_samples, 2) uniform points on each element’s annulus.

get_area_data()[source]

(N, 2) of [inner_radius, outer_radius] per element.

area_fn(data)[source]

Annular area from one element’s [inner_radius, outer_radius].

__init__(radii, inner_radii)
class iactrace.core.PolygonAperture[source]

Bases: Aperture

Convex polygon aperture defined by vertices.

All elements in a group must have the same number of vertices.

Variables:
  • vertices (ndarray (N, K, 2)) – Polygon vertices per element, CCW order.

  • n_vertices (int) – Number of vertices per polygon; static, and the same for every element.

vertices = <dataclasses._MISSING_TYPE object>
n_vertices = <dataclasses._MISSING_TYPE object>
check(x, y, element_idx)[source]
sample(key, n_samples)[source]

(N, n_samples, 2) uniform points on each element’s polygon.

get_area_data()[source]

The (N, K, 2) vertices; area needs nothing else.

area_fn(data)[source]

Polygon area from one element’s (K, 2) vertices.

__init__(vertices, n_vertices)

Interactions

class iactrace.core.Interaction[source]

Bases: Module

Abstract base for optical interaction modules.

abstract property interaction_type
abstract property kind

User-facing element kind.

focal_scale(n_outside=1.0, wavelength=400.0)[source]

Curvature<->focal-length scale factor for this interaction.

curvature = 1 / (scale * focal_length): 2 for mirrors, n(wavelength) - n_outside for a single refracting surface at the design wavelength.

abstractmethod apply(directions, normals, points, element_idx, current_n, wavelength=None)[source]

Apply the interaction at hit points.

Parameters:
  • directions – Per-ray geometry at the surface hit.

  • normals – Per-ray geometry at the surface hit.

  • points – Per-ray geometry at the surface hit.

  • element_idx – Per-ray geometry at the surface hit.

  • current_n – Per-ray refractive index of the medium the ray is currently propagating in. Used as the incident-side index for refraction physics, so OPL is exact even through stacked refractive surfaces.

  • wavelength – Per-ray wavelength.

Returns:

(new_directions, new_positions, coefficients, opl_internal, new_n).

Return type:

tuple

__init__()
class iactrace.core.ReflectInteraction[source]

Bases: Interaction

Reflection interaction for mirrors.

Per-ray coefficient:

reflectivity[idx] * reflectivity_curve(cos_theta_i, idx)

With no curve (the default) the angular factor is unity, i.e. an ideal angle- and wavelength-independent mirror of response reflectivity. :ivar reflectivity_curve: R(theta, lambda) response curve, or None

for a flat angular and spectral response.

Variables:

reflectivity (ndarray (N,)) – Per-element bulk multiplier in [0, 1]. Operations such as set_reflectivity write here, leaving the curve untouched.

reflectivity_curve = <dataclasses._MISSING_TYPE object>
reflectivity = <dataclasses._MISSING_TYPE object>
property interaction_type
property kind

User-facing element kind.

focal_scale(n_outside=1.0, wavelength=400.0)[source]

Curvature<->focal-length scale factor for this interaction.

curvature = 1 / (scale * focal_length): 2 for mirrors, n(wavelength) - n_outside for a single refracting surface at the design wavelength.

with_reflectivity(reflectivity)[source]

Return a copy with the bulk reflectivity replaced.

scaled_reflectivity(factor)[source]

Return a copy with the bulk reflectivity scaled by factor.

apply(directions, normals, points, element_idx, current_n, wavelength=None)[source]

Apply the interaction at hit points.

Parameters:
  • directions – Per-ray geometry at the surface hit.

  • normals – Per-ray geometry at the surface hit.

  • points – Per-ray geometry at the surface hit.

  • element_idx – Per-ray geometry at the surface hit.

  • current_n – Per-ray refractive index of the medium the ray is currently propagating in. Used as the incident-side index for refraction physics, so OPL is exact even through stacked refractive surfaces.

  • wavelength – Per-ray wavelength.

Returns:

(new_directions, new_positions, coefficients, opl_internal, new_n).

Return type:

tuple

__init__(reflectivity_curve, reflectivity)
class iactrace.core.RefractInteraction[source]

Bases: Interaction

Single-surface refraction interaction for lenses.

Per-ray coefficient:

transmittance[idx] * angular_response(cos_theta_i)

With no curve (the default) the angular response is fresnel_unpolarized evaluated from current_n (the medium the ray is currently in) and the far-side index of this surface. Snell’s law is always applied to bend the ray.

Semantically, this represents the ray crossing a single interface from one medium into another. A real glass body (e.g. a biconvex lens) should be modelled as two consecutive RefractInteraction stages, front then back surface, and the render loop’s per-ray medium tracker carries the correct index through the glass interior between them.

Variables:
index = <dataclasses._MISSING_TYPE object>
transmittance_curve = <dataclasses._MISSING_TYPE object>
transmittance = <dataclasses._MISSING_TYPE object>
property interaction_type
property kind

User-facing element kind.

focal_scale(n_outside=1.0, wavelength=400.0)[source]

Curvature<->focal-length scale factor for this interaction.

curvature = 1 / (scale * focal_length): 2 for mirrors, n(wavelength) - n_outside for a single refracting surface at the design wavelength.

with_index(index)[source]

Return a copy with the refractive index replaced.

with_transmittance(transmittance)[source]

Return a copy with the bulk transmittance replaced (clipped to [0, 1]).

scaled_transmittance(factor)[source]

Return a copy with the bulk transmittance scaled by factor.

apply(directions, normals, points, element_idx, current_n, wavelength=None)[source]

Apply the interaction at hit points.

Parameters:
  • directions – Per-ray geometry at the surface hit.

  • normals – Per-ray geometry at the surface hit.

  • points – Per-ray geometry at the surface hit.

  • element_idx – Per-ray geometry at the surface hit.

  • current_n – Per-ray refractive index of the medium the ray is currently propagating in. Used as the incident-side index for refraction physics, so OPL is exact even through stacked refractive surfaces.

  • wavelength – Per-ray wavelength.

Returns:

(new_directions, new_positions, coefficients, opl_internal, new_n).

Return type:

tuple

__init__(index, transmittance_curve, transmittance)
class iactrace.core.SlabInteraction[source]

Bases: Interaction

Parallel-sided slab (window) interaction.

Per-ray coefficient:

transmittance[idx] * angular_response

With no curve (the default) the angular response is the standard Fresnel product at the two faces: by parallel-slab symmetry and Stokes reciprocity, both faces share the same single-face Fresnel coefficient so the result simplifies to T_face^2. A curve overrides that with a T(theta, lambda) for the complete slab, fully replacing the Fresnel product. The TIR mask from the underlying geometry gates out invalid rays.

The ray enters from its current medium (current_n), refracts into the slab material, traverses it, and refracts back out into the same medium; slabs assume the ambient is symmetric across them, which is the usual case for a window. opl_internal is the per-ray n_in * L inside the slab.

Variables:
  • index (iactrace.core.refractive_index.RefractiveIndex) – Per-element slab refractive index, a RefractiveIndex model evaluated per ray at that ray’s wavelength. A constant window is ConstantIndex.

  • thickness (ndarray (N,)) – Per-element slab thickness.

  • transmittance_curve (iactrace.core.responses.ResponseCurve | None) – T(theta, lambda) response curve for the whole slab, or None for the bare-window Fresnel product.

  • transmittance (ndarray (N,)) – Per-element bulk multiplier in [0, 1].

index = <dataclasses._MISSING_TYPE object>
thickness = <dataclasses._MISSING_TYPE object>
transmittance_curve = <dataclasses._MISSING_TYPE object>
transmittance = <dataclasses._MISSING_TYPE object>
property interaction_type
property kind

User-facing element kind.

with_index(index)[source]

Return a copy with the refractive index replaced.

with_thickness(thickness)[source]

Return a copy with the slab thickness replaced.

with_transmittance(transmittance)[source]

Return a copy with the bulk transmittance replaced (clipped to [0, 1]).

scaled_transmittance(factor)[source]

Return a copy with the bulk transmittance scaled by factor.

apply(directions, normals, points, element_idx, current_n, wavelength=None)[source]

Apply the interaction at hit points.

Parameters:
  • directions – Per-ray geometry at the surface hit.

  • normals – Per-ray geometry at the surface hit.

  • points – Per-ray geometry at the surface hit.

  • element_idx – Per-ray geometry at the surface hit.

  • current_n – Per-ray refractive index of the medium the ray is currently propagating in. Used as the incident-side index for refraction physics, so OPL is exact even through stacked refractive surfaces.

  • wavelength – Per-ray wavelength.

Returns:

(new_directions, new_positions, coefficients, opl_internal, new_n).

Return type:

tuple

__init__(index, thickness, transmittance_curve, transmittance)
class iactrace.core.InteractionType[source]

Bases: Enum

Type of optical interaction at a surface.

REFLECT = 'reflect'
REFRACT = 'refract'
SLAB = 'slab'

Response Curves

Angle- and wavelength-dependent reflectivity / transmittance applied at an interaction.

class iactrace.core.ResponseCurve[source]

Bases: Module

Abstract base for an optical elements R(theta, lambda) response.

A response curve maps each rays incidence-angle cosine and wavelength to a coefficient in [0, 1].

All subclasses return an array broadcastable to cos_theta_i.shape.

__init__()
class iactrace.core.ConstantResponse[source]

Bases: ResponseCurve

Angle- and wavelength-independent per-element response.

Variables:

values (ndarray (N,)) – Per-element coefficient in [0, 1].

values = <dataclasses._MISSING_TYPE object>
__init__(values)
class iactrace.core.TabulatedResponse[source]

Bases: ResponseCurve

Bilinear interpolation over a shared (angle, wavelength) grid.

Each ray’s coefficient is read from a per-element cos(angle) x wavelength table, linearly interpolated in both axes and clamped at the grid edges (matching jax.numpy.interp).

Variables:
  • cos_table (ndarray (Kc,)) – cos(angle) axis, sorted ascending. cos_theta_i = 1 -> normal incidence, 0 -> grazing.

  • wl_table (ndarray (Kw,)) – Wavelength axis, sorted ascending. Same units as wavelength. Length 1 for a wavelength-independent curve.

  • values (ndarray (N, Kc, Kw)) – Per-element coefficient grid.

cos_table = <dataclasses._MISSING_TYPE object>
wl_table = <dataclasses._MISSING_TYPE object>
values = <dataclasses._MISSING_TYPE object>
classmethod from_degrees(angles_deg, values, n_elements, *, wavelengths=None)[source]

Build a TabulatedResponse from angles given in degrees.

Both axes are sorted internally, so angles_deg (Kc,) and wavelengths (Kw,) may arrive in any order.

Parameters:
  • values (ndarray (Kc,)) – Without wavelengths, an angle curve –. broadcast to all elements, or (N, Kc) per element. With wavelengths, an (angle, wavelength) grid – (Kc, Kw) broadcast, or (N, Kc, Kw) per element.

  • wavelengths – Omit for a wavelength-independent curve.

classmethod from_wavelengths(wavelengths, values, n_elements)[source]

Build an angle-flat R(lambda) curve from wavelength samples.

The convenience wrapper for the wavelength-only case.

__init__(cos_table, wl_table, values)

BSDF (surface scattering)

class iactrace.core.BSDF[source]

Bases: Module

Abstract base for surface scattering models.

Subclasses implement _sample_perturbation, returning (angles, scale); the base class builds the tangent frame and applies the perturbation.

perturb_normals(normals, key, element_idx=None)[source]

Perturb (…, 3) surface normals.

Works for any leading shape: (n_rays, 3) with a per-ray element_idx, or (N, S, 3) with element_idx=None when the element dimension is already present.

__init__()
class iactrace.core.GaussianBSDF[source]

Bases: BSDF

Single-Gaussian surface roughness model.

Perturbs surface normals by Gaussian-distributed random angles. This is the standard model for surface microroughness.

Variables:

scale (ndarray (N,)) – Per-element roughness sigma in arcseconds. Zero means perfect specular (no perturbation).

scale = <dataclasses._MISSING_TYPE object>
__init__(scale)
class iactrace.core.DoubleGaussianBSDF[source]

Bases: BSDF

Mixture of two Gaussians for surfaces with multi-scale roughness.

Models surfaces that have both fine-scale microroughness (narrow component) and broader scattering from mid-spatial-frequency errors (wide component). Each ray’s perturbation is drawn from the narrow Gaussian with probability (1 - mix_weight) or from the wide Gaussian with probability mix_weight.

Variables:
  • scale_narrow (ndarray (N,)) – Per-element narrow-component sigma in arcseconds.

  • scale_wide (ndarray (N,)) – Per-element wide-component sigma in arcseconds.

  • mix_weight (ndarray (N,)) – Per-element probability of the wide component, in [0, 1].

scale_narrow = <dataclasses._MISSING_TYPE object>
scale_wide = <dataclasses._MISSING_TYPE object>
mix_weight = <dataclasses._MISSING_TYPE object>
__init__(scale_narrow, scale_wide, mix_weight)

Optical Physics

Functions for ray-surface interactions:

iactrace.core.reflect(direction, normal)[source]

Reflect a ray off a surface: (reflected direction, cos_i).

iactrace.core.refract(direction, normal, n1, n2)[source]

Refract a ray through an interface by Snell’s law.

Returns (refracted, cos_i, tir). Rays arriving from either side are handled by flipping the outward normal as needed, so cos_i is always the non-negative incidence cosine with the ambient-vs-internal side correctly resolved. On total internal reflection tir is True and the reflected direction is returned in place of the refracted one.

Parameters:
  • direction (ndarray (3,)) – Ray direction, normalized.

  • normal (ndarray (3,)) – Surface normal, normalized, pointing outward.

  • n1 – Refractive index of the incident and transmitted media.

  • n2 – Refractive index of the incident and transmitted media.

iactrace.core.refract_slab(direction, normal, position, n_out, n_in, thickness)[source]

Refract a ray through a parallel-sided slab (window).

Enters through the front face at position (whose outward normal is given), crosses the bulk, and exits the far face.

Parameters:
  • direction (ndarray (3,)) – Ray direction, normalized.

  • normal (ndarray (3,)) – Front-surface normal, pointing outward.

  • position (ndarray (3,)) – Entry point in world coordinates.

  • n_out – Refractive index of the ambient medium and of the slab material.

  • n_in – Refractive index of the ambient medium and of the slab material.

  • thickness – Slab thickness, in the units of position.

Returns:

(exit_direction, exit_position, cos_i, valid, path_length). cos_i is the incidence cosine on the slab from outside, valid is False if either face totally internally reflected, and path_length is the geometric distance travelled inside.

Return type:

tuple

iactrace.core.fresnel_unpolarized(cos_theta_i, n1, n2)[source]

Unpolarized Fresnel coefficients (R, T) for a bare dielectric interface.

The average of s- and p-polarized intensities, with T = 1 - R. Used as the implicit default by RefractInteraction and SlabInteraction when no explicit ResponseCurve is given.

Surfaces

Surface-figure models. SurfaceGroup is the base; the concrete groups below can be combined with SumSurfaceGroup (e.g. an aspheric base plus a per-facet Zernike figure error).

class iactrace.core.SurfaceGroup[source]

Bases: Module

Abstract base for batched surface parameters of N optical elements.

A SurfaceGroup stores per-element surface geometry and provides: - Sag/normal computation for the transform pipeline (vmapped per element) - Per-element sag and ray intersection for rendering and visualization

offsets = <dataclasses._MISSING_TYPE object>
compute_sag_and_normal_at(x, y)[source]

(point, normal) at (x, y) for a single element, both (3,).

The normal is unit length, obtained by autodiff of the sag.

sag_at(element_idx, x, y)[source]

Sag z(x, y) of one element, relative to its decenter.

Local frame. Used by the visualization module for mesh generation.

intersect_t_at(element_idx, ray_origin, ray_direction)[source]

Ray parameter and local landing point, without evaluating the surface.

Returns (t, x, y): the intersection distance (inf on a miss) and the local in-surface coordinates of the landing point. On a miss the coordinates are those of the ray’s closest approach, so they stay finite for downstream masking.

intersect_at(element_idx, ray_origin, ray_direction)[source]

Intersect a local-frame ray with one element: (t, point, normal).

Generic over the surface type – _intersect_t resolves the ray parameter (closed-form for pure conics, Newton-refined otherwise) and point / normal follow from the sag at the hit. On a miss (t = inf) both are evaluated at the ray origin, so they stay finite for downstream masking.

__init__(offsets)
class iactrace.core.SumSurfaceGroup[source]

Bases: SurfaceGroup

Composite surface whose sag is the sum of its components’ sags.

The composite’s own offsets decenter the whole patch; component offsets (usually zero) are applied first, inside each component’s _sag_local.

Variables:
  • components (tuple) – Tuple of component SurfaceGroup instances, each sized to the same N.

  • offsets (ndarray (N, 2)) – Per-element in-surface decenter for the composite. (inherited).

__init__(components, offsets=None)[source]
components = <dataclasses._MISSING_TYPE object>
class iactrace.core.AsphericSurfaceGroup[source]

Bases: SurfaceGroup

Batched aspheric surface parameters for N optical elements.

A conic + even-polynomial surface used by OpticalElementGroup. When sliced/vmapped to a single element, each becomes a scalar-parameter surface; the generic SurfaceGroup machinery then handles sag, normal, and intersection. The conic provides a closed-form intersection initial guess via _t_guess.

Variables:
  • curvatures (ndarray (N,)) – Per-element curvatures.

  • conics (ndarray (N,)) – Per-element conic constants.

  • aspherics (ndarray (N, K)) – Per-element even aspheric coefficients [A4, A6, …]; column i multiplies r^(2i + 4). See sag_raw.

  • offsets (ndarray (N, 2)) – Per-element in-surface decenter (inherited).

curvatures = <dataclasses._MISSING_TYPE object>
conics = <dataclasses._MISSING_TYPE object>
aspherics = <dataclasses._MISSING_TYPE object>
__init__(offsets, curvatures, conics, aspherics)
class iactrace.core.ZernikeSurfaceGroup[source]

Bases: SurfaceGroup

Standalone Zernike figure surface for N optical elements.

Represents a surface whose height is a sum of RMS-normalized Noll Zernike polynomials, independent of any conic/aspheric base. Use it on its own to describe a pure figure-error surface, or as a term inside a SumSurfaceGroup to add a measured / random figure error on top of another surface.

The normal is obtained by autodiff of the sag (inherited from SurfaceGroup), and the intersection uses the inherited tangent-plane initial guess.

Variables:
  • coeffs (ndarray (N, J)) – Per-element Noll coefficients in metres. With J <= 11. Column m is Noll index m + 1 (Z1 = piston).

  • r_norm (ndarray (N,)) – Per-element normalization radius in metres. rho = 1 at this radius.

  • offsets (ndarray (N, 2)) – Per-element in-surface decenter (inherited).

__init__(coeffs, r_norm, offsets=None)[source]
coeffs = <dataclasses._MISSING_TYPE object>
r_norm = <dataclasses._MISSING_TYPE object>
class iactrace.core.FreeformSurfaceGroup[source]

Bases: SurfaceGroup

Per-element freeform surface defined by a bicubically interpolated grid.

Each element carries a regular (H, W) height map sampled over a rectangular domain; the sag at arbitrary (x, y) is the Catmull-Rom bicubic interpolation of that map.

For a strongly curved freeform, compose it on top of an AsphericSurfaceGroup in a SumSurfaceGroup (base term first) so the conic supplies the intersection initial guess.

Variables:
  • grid_z (ndarray (N, H, W)) – Per-element height samples in metres. grid_z[n, j, i] is the height of element n at column i (x), row j (y).

  • y0 (x0,) – Per-element grid origin — the coordinate of column / row 0.

  • dy (dx,) – Per-element grid spacing along x / y.

  • offsets (ndarray (N, 2)) – Per-element in-surface decenter (inherited).

__init__(grid_z, x0, y0, dx, dy, offsets=None)[source]
grid_z = <dataclasses._MISSING_TYPE object>
x0 = <dataclasses._MISSING_TYPE object>
y0 = <dataclasses._MISSING_TYPE object>
dx = <dataclasses._MISSING_TYPE object>
dy = <dataclasses._MISSING_TYPE object>
classmethod from_extent(grid_z, half_width, half_height, offsets=None)[source]

Build from a grid centred on the origin spanning a given extent.

The grid columns span [-half_width, half_width] and rows span [-half_height, half_height]. half_width / half_height may be scalar (shared) or per-element (N,).

iactrace.core.sag(x, y, offset, curvature, conic, aspheric)[source]

Sag z(x, y) in local mirror coordinates, relative to offset.

iactrace.core.compute_sag_and_normal(x, y, offset, curvature, conic, aspheric)[source]

(point, normal) at (x, y), both (3,), from loose parameters.

iactrace.core.zernike_terms(u, v)[source]

RMS-normalized Noll Zernike polynomials Z1..Z11.

Evaluated in normalized Cartesian coordinates u = x / r_norm and v = y / r_norm (so the unit disk is u^2 + v^2 <= 1). The terms are written as Cartesian polynomials rather than via (rho, phi) so the gradient is smooth everywhere, including the origin.

With the Noll RMS normalization each term has unit RMS over the unit disk, so a coefficient in metres equals that aberration’s RMS surface contribution in metres. If your surface is not circular, you have to rescale.

Parameters:
  • u (scalar or array) – Normalized x-coordinate.

  • v (scalar or array) – Normalized y-coordinate.

Returns:

  • Array with the 11 lowest Noll terms stacked on the last axis, in Noll

  • order – piston, tilt x/y, defocus, astigmatism (oblique/vertical), coma

  • (vertical/horizontal), trefoil (vertical/oblique), primary spherical.

iactrace.core.bicubic_interp(grid, u, v)[source]

Bicubic (Catmull-Rom) interpolation of an (H, W) height grid.

grid[j, i] is the height at column i (x) and row j (y); u and v are fractional column / row coordinates. Exact at grid nodes. Queries outside the grid clamp to the edge (flat extrapolation), which keeps the Newton intersection well-behaved when it strays off the patch.

iactrace.core.sag_raw(x, y, curvature, conic, aspheric)[source]

Sag z(x, y) of the standard even asphere, without any decenter:

z(r) = c r^2 / (1 + sqrt(1 - (1 + k) c^2 r^2))
  • A4 r^4 + A6 r^6 + A8 r^8 + …

curvature is c (1/radius), conic is k, and aspheric is [A4, A6, …] of shape (K,).

iactrace.core.N_ZERNIKE = 11

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4

Intersection Functions

Geometric ray-primitive intersection tests (in iactrace.core.intersections):

iactrace.core.intersect_plane(ray_origin, ray_direction, plane_center, plane_rotation)[source]

Intersect a ray with a plane, and locate the hit within it.

Unlike the other kernels this returns (xy, t): the in-plane coordinates of the hit along plane_rotation’s first two columns (its third is the normal), and the ray parameter. A ray that never meets the plane gets t = inf and xy = _MISS_PLANE_XY.

iactrace.core.intersect_sphere(ray_origin, ray_direction, center, radius)[source]

Intersect ray with sphere.

Parameters:
  • center (ndarray (3,)) – Sphere center.

  • radius (scalar) – Sphere radius.

iactrace.core.intersect_cylinder(ray_origin, ray_direction, p1, p2, radius)[source]

Intersect ray with a finite cylinder, end caps included.

Parameters:
  • p1 (ndarray (3,)) – First endpoint of cylinder axis.

  • p2 (ndarray (3,)) – Second endpoint of cylinder axis.

  • radius (scalar) – Cylinder radius.

iactrace.core.intersect_open_cylinder(ray_origin, ray_direction, p1, p2, radius)[source]

Intersect ray with finite cylinder without end caps (curved surface only).

Parameters:
  • p1 (ndarray (3,)) – First endpoint of cylinder axis.

  • p2 (ndarray (3,)) – Second endpoint of cylinder axis.

  • radius (scalar) – Cylinder radius.

iactrace.core.intersect_box(ray_origin, ray_direction, p1, p2)[source]

Intersect ray with AABB box.

Parameters:
  • p1 (ndarray (3,)) – lower edge of the bounding box.

  • p2 (ndarray (3,)) – upper diagonal edge of the bounding box.

iactrace.core.intersect_oriented_box(ray_origin, ray_direction, center, half_extents, rotation)[source]

Intersect ray with oriented bounding box.

Parameters:
  • center (ndarray (3,)) – Box center.

  • half_extents (ndarray (3,)) – Half-sizes along local axes.

  • rotation (ndarray (3, 3)) – Rotation matrix transforming local to world coordinates.

iactrace.core.intersect_triangle(ray_origin, ray_direction, v0, v1, v2)[source]

Intersect ray with triangle using Moeller-Trumbore algorithm.

Parameters:
  • v0 (ndarray (3,)) – Triangle vertices.

  • v1 (ndarray (3,)) – Triangle vertices.

  • v2 (ndarray (3,)) – Triangle vertices.

iactrace.core.intersect_conic(ray_origin, ray_direction, curvature, conic)[source]

Closed-form ray-conic intersection, on the sag branch only.

Parameters:
  • curvature – Surface curvature (1/radius)

  • conic – Conic constant (0=sphere, -1=paraboloid, <-1=hyperboloid, >-1=ellipsoid)

For a surface with no closed-form root, the generic Newton solver:

iactrace.core.newton_raphson_intersect(sag_fn, ray_origin, ray_direction, t_init=None, max_iter=10, tol=None)[source]

Ray-surface intersection by Newton-Raphson, for any sag function.

The generic fallback for a surface with no closed-form root. Rays are in the surface’s local coordinates.

Parameters:
  • sag_fn – Callable (x, y) -> z giving surface height.

  • t_init – Initial guess; None starts from the z = 0 plane.

  • max_iter – Maximum iterations.

  • tol – Absolute residual tolerance on z - sag(x, y), in the coordinates’ own units. None derives it per ray.

Returns:

  • (t, hit_xy, valid) (the ray parameter (inf if no intersection),)

  • the (2,) in-surface coordinates of the hit, and whether it

  • converged to a real intersection.

iactrace.core.is_hit(t)[source]

Whether a ray parameter denotes a real intersection.

Obstruction Groups

Classes for modeling ray obstructions:

class iactrace.core.ObstructionGroup[source]

Bases: Module

Base class for grouped obstructions.

abstractmethod __len__()[source]

Number of obstruction primitives in this group.

intersect(ray_origin, ray_direction)[source]

Returns min t across all primitives in group.

intersect_batch(origins, directions)[source]

Nearest hit distance per ray, for (n_rays, 3) rays.

Same answer as vmap(self.intersect), but chooses how to go over the primitives based on how many rays there are.

__init__()
class iactrace.core.CylinderGroup[source]

Bases: ObstructionGroup

Group of cylinders for batched intersection.

__init__(p1, p2, r)[source]
p1 = <dataclasses._MISSING_TYPE object>
p2 = <dataclasses._MISSING_TYPE object>
r = <dataclasses._MISSING_TYPE object>
class iactrace.core.OpenCylinderGroup[source]

Bases: ObstructionGroup

Group of open cylinders (no end caps) for batched intersection.

__init__(p1, p2, r)[source]
p1 = <dataclasses._MISSING_TYPE object>
p2 = <dataclasses._MISSING_TYPE object>
r = <dataclasses._MISSING_TYPE object>
class iactrace.core.BoxGroup[source]

Bases: ObstructionGroup

Group of axis-aligned boxes for batched intersection.

__init__(p1, p2)[source]
p1 = <dataclasses._MISSING_TYPE object>
p2 = <dataclasses._MISSING_TYPE object>
class iactrace.core.SphereGroup[source]

Bases: ObstructionGroup

Group of spheres for batched intersection.

__init__(centers, radii)[source]
centers = <dataclasses._MISSING_TYPE object>
radii = <dataclasses._MISSING_TYPE object>
class iactrace.core.OrientedBoxGroup[source]

Bases: ObstructionGroup

Group of oriented boxes for batched intersection.

__init__(centers, half_extents, rotations)[source]
centers = <dataclasses._MISSING_TYPE object>
half_extents = <dataclasses._MISSING_TYPE object>
rotations = <dataclasses._MISSING_TYPE object>
class iactrace.core.TriangleGroup[source]

Bases: ObstructionGroup

Group of triangles for batched intersection.

__init__(v0, v1, v2)[source]
v0 = <dataclasses._MISSING_TYPE object>
v1 = <dataclasses._MISSING_TYPE object>
v2 = <dataclasses._MISSING_TYPE object>

Transforms

Coordinate transformation utilities:

iactrace.core.euler_to_matrix(tip_tilt_rotation)[source]

(3, 3) rotation from Euler angles (rx, ry, rz) in degrees.

Applied extrinsically, x then y then z: R = Rz(rz) @ Ry(ry) @ Rx(rx).

Aperture Sampling

Uniform sampling over an element’s aperture, used when a render generates its primary-surface rays.

iactrace.core.sample_annulus(key, inner_radius, outer_radius, shape)[source]

Uniform (*shape, 2) points in the annulus between the two radii.

iactrace.core.sample_polygon(key, vertices, shape)[source]

Uniform (*shape, 2) points over the convex polygon vertices (n, 2).

Numerical Tolerances

Resolution-relative floors, derived from the working dtype.

iactrace.core.dir_tol(x)[source]
iactrace.core.len_rel(x)[source]