Telescope Operations¶
IACTrace provides a functional API for modifying telescopes. All operations return new telescope instances rather than mutating in place, enabling reproducible simulations and compatibility with JAX transformations.
Operations are addressed by stage — the integer optical_stage
of the target group. The split between mirrors and lenses inside the
telescope YAML is purely a storage detail; in code, you only ever talk
in stages. Stage 0 is the primary; the renderer walks stages in
ascending order.
Note
Operations are available both as standalone functions in
iactrace.telescope.operations and as convenience methods on
the Telescope class. The method form is
recommended:
# Recommended: method form
telescope = telescope.apply_roughness(stage=0, sigma=24.0)
# Alternative: function form
from iactrace.telescope import operations as ops
telescope = ops.apply_roughness(telescope, stage=0, sigma=24.0)
Generic operations¶
These work on any kind of stage (mirror, lens, or slab).
Surface Roughness
Roughness broadens the PSF by perturbing surface normals at intersection time. Specified in arcseconds RMS:
telescope = telescope.apply_roughness(stage=0, sigma=24.0)
Misalignment
Random Gaussian tip/tilt on element orientations (arcseconds):
import jax
key = jax.random.key(42)
telescope = telescope.apply_misalignment(
stage=0,
sigma_h=10.0, # horizontal tip
sigma_v=10.0, # vertical tilt
key=key,
)
Position and Orientation
Set element positions and orientations directly:
import jax.numpy as jnp
current_pos = telescope.stage(0).positions
new_pos = current_pos.at[:, 2].add(0.001) # shift +1mm in z
telescope = telescope.set_positions(stage=0, positions=new_pos)
new_rot = jnp.zeros_like(telescope.stage(0).rotations)
telescope = telescope.set_rotations(stage=0, rotations=new_rot)
Displacement
Random Gaussian z-displacement (e.g. dish errors):
telescope = telescope.apply_displacement(
stage=0, sigma_z=0.001, key=key,
)
Surface parameters
Curvature, conic constant, aspheric coefficients on the
AsphericSurfaceGroup underlying each stage:
n = telescope.stage(0).n_elements
telescope = telescope.set_curvatures(
stage=0, curvatures=jnp.full(n, 1.0 / 30.0),
)
telescope = telescope.scale_curvatures(stage=0, factor=1.01)
telescope = telescope.offset_curvatures(stage=0, offset=0.001)
telescope = telescope.set_conics(stage=0, conics=jnp.full(n, -1.0))
telescope = telescope.set_aspherics(stage=0, aspherics=jnp.zeros((n, 4)))
telescope = telescope.apply_conic_error(
stage=0, sigma=0.01, key=key,
)
telescope = telescope.apply_aspheric_error(
stage=0,
sigmas=jnp.array([1e-6, 1e-8, 1e-10, 1e-12]),
key=key,
)
Zernike figure errors
Add random Zernike surface-figure error, composed onto the stage’s surface
(an aspheric surface becomes an asphere + Zernike sum). sigmas are RMS
surface errors in metres, one per Noll mode (index 0 = piston, 1/2 tilt,
3 defocus, 4/5 astigmatism, 6/7 coma, 8/9 trefoil, 10 spherical; up to 11):
telescope = telescope.apply_zernike_error(
stage=0,
sigmas=jnp.array([0.0, 0.0, 0.0, 1e-6, 1e-6]), # defocus + astigmatism
key=key,
)
Named single-aberration shortcuts take one RMS sigma (metres) applied to
both components of the aberration:
telescope = telescope.apply_astigmatism(stage=0, sigma=1e-6, key=key) # Z5/Z6
telescope = telescope.apply_coma(stage=0, sigma=1e-6, key=key) # Z7/Z8
telescope = telescope.apply_trefoil(stage=0, sigma=1e-6, key=key) # Z9/Z10
Resample Monte-Carlo
Refresh the sampling key on a single stage:
telescope = telescope.resample(stage=0, key=jax.random.key(7))
Kind-specific operations¶
These validate the kind at the requested stage and raise
ValueError if applied to the wrong kind.
Reflectivity (mirror only)
telescope = telescope.set_reflectivity(stage=0, reflectivity=0.95)
telescope = telescope.scale_reflectivity(stage=0, factor=0.9)
Transmittance (lens or slab only)
telescope = telescope.set_transmittance(stage=2, transmittance=0.98)
telescope = telescope.scale_transmittance(stage=2, factor=0.95)
Refractive index (lens or slab only)
telescope = telescope.set_refractive_index(stage=2, n_inside=1.52)
Slab thickness (slab only)
telescope = telescope.set_thickness(stage=2, thickness=0.005)
Focal length (mirror or lens, kind-dispatched formula)
For mirrors, c = 1 / (2 f). For single-surface refractive lenses,
c = 1 / ((n_inside - n_outside) f), where n_outside is a design-time
ambient-index assumption passed to the operation (default 1.0; the lens
itself stores no ambient index – the render loop reads it dynamically from
each ray’s current medium). Slabs raise.
telescope = telescope.set_focal_lengths(
stage=0, focal_lengths=jnp.full(n, 15.0),
)
# Manufacturing tolerances
telescope = telescope.apply_focal_error(
stage=0, sigma=0.01, key=key, relative=True,
)
Camera frame operations¶
The telescope carries the camera frame (where rays are delivered to the
Camera). The sensors themselves live on the camera
and are configured separately.
telescope = telescope.focus(delta_z=-0.005)
telescope = telescope.set_camera_position(jnp.array([0.0, 0.0, 14.95]))
telescope = telescope.set_camera_rotation(jnp.array([0.0, 1.0, 0.0]))
Sensor-side adjustments live on the camera:
camera = camera.set_sensor_positions(sensor_idx=0, positions=new_pos)
camera = camera.set_sensor_rotations(sensor_idx=0, rotations=new_rot)
Obstruction operations¶
Manage mechanical structures that block rays:
from iactrace.core import CylinderGroup
mast = CylinderGroup(
p1=jnp.array([[0.0, 0.0, 0.0]]),
p2=jnp.array([[0.0, 0.0, 15.0]]),
r=jnp.array([0.05]),
)
telescope = telescope.add_obstruction(mast)
telescope = telescope.remove_obstruction(group_idx=0)
telescope = telescope.clear_obstructions()
Stage discovery and summary¶
for s in telescope.stage_indices():
g = telescope.stage(s)
print(f"stage {s}: {g.kind}, {g.n_elements} elements")
mirror_stages = telescope.stages_of_kind("mirror")
lens_stages = telescope.stages_of_kind("lens")
info = telescope.get_info()
print(f"Telescope: {info['name']}")
print(f" {info['n_mirror_elements']} mirror elements")
print(f" {info['n_lens_elements']} lens elements")
print(f" {info['n_obstructions']} obstructions")
Chaining operations¶
key1, key2 = jax.random.split(key)
telescope = (
telescope
.apply_roughness(stage=0, sigma=24.0)
.apply_misalignment(stage=0, sigma_h=10.0, sigma_v=10.0, key=key1)
.apply_focal_error(stage=0, sigma=0.01, key=key2, relative=True)
.focus(-0.005)
)
Next Steps¶
Example Gallery — detailed examples for specific use cases
iactrace.telescope — full API reference