Giant Magellan Telescope — Rust Framework

Integrated Modeling for the
Giant Magellan Telescope

A high-performance actor-model framework built in Rust for assembling, simulating, and running the GMT control system. Compose multi-rate concurrent actors with a type-safe domain-specific language.

View on GitHub Read the Guide
Why gmt_dos-actors

Everything you need to model a telescope

From structural dynamics to optics, all wired together through a safe, composable actor graph.

Concurrent Actor Model

Each subsystem runs as an independent async Tokio actor. Actors communicate via typed channels—no shared mutable state, no data races.

actorscript! DSL

Declare actor graphs with a concise, readable macro. Wiring, sampling-rate negotiation, and feedback loops are resolved at compile time.

Multi-rate Sampling

Different subsystems run at different rates—FEM at 8 kHz, actuators at 100 Hz, optics at 1 kHz. Samplers are inserted automatically.

🔭
GMT Integration

First-class clients for every telescope subsystem: M1/M2 mirrors, mount control, ASM voice coils, wind loads, and wavefront sensors.

🦀
Type-safe I/O

Every signal carries a Rust type. Mismatched connections are compile errors. Physics units are encoded in types—arcseconds, nanometers, and more.

📊
Built-in Logging

Annotate any output with ~ to stream it live to a scope display, or with $ to record it to Apache Arrow / Parquet.

GPU Acceleration

The structural dynamics solver (gmt_dos-clients_fem with the cuda feature) and the full optical simulation (gmt_dos-clients_crseo) are CUDA-accelerated, enabling high-fidelity real-time models.

🌐
Distributed Models

Split a model across multiple scripts — on the same machine or across a network — using the transceiver client. Scripts exchange actor data over the QUIC protocol with minimal latency.

💨
CFD Simulations Database

Wind load forces and dome-seeing wavefront errors are sourced from the GMT CFD simulations database at cfd.gmto.im, directly consumed by the windloads and domeseeing clients.

☁️
Flexible Deployment

Integrated models can run self-hosted on local hardware or on GMTO IM-owned cloud machines, enabling collaborative simulation campaigns without local data or compute constraints.

🔌
Extensible Client Interface

Bring your own subsystem by implementing the Read, Update, and Write traits from gmt_dos-actors-clients_interface. Any Rust struct that satisfies the interface becomes a first-class actor in the graph.

Domain-Specific Language

Build models with actorscript!

A procedural macro that turns a concise dataflow description into a fully wired, asynchronous actor graph.

Syntax at a glance

Each line in actorscript! is a flow: a sampling-rate multiplier followed by a chain of actors connected by arrows. The macro generates all the channel scaffolding, spawns Tokio tasks, and resolves rate mismatches for you.

TokenMeaning
1: Sampling-rate multiplier (1× nominal, 8×, 80×, …)
actor[Type] Actor with its output data type
-> Data flow — connect output to next actor's input
! Send immediately — breaks feedback-loop deadlocks
~ Scope — stream the signal live to a real-time display
$ Enable data logging to Apache Arrow / Parquet
{sys::Sub} Scoped access to a sub-actor inside a system
use gmt_dos_actors::actorscript;
use gmt_dos_clients::signals::{Signal, Signals};
use gmt_dos_clients_io::{
    gmt_m1::M1RigidBodyMotions,
    gmt_m2::M2RigidBodyMotions,
    mount::{MountEncoders, MountSetPoint, MountTorques},
    optics::TipTilt,
};
use gmt_dos_clients_lom::LinearOpticalModel;
use gmt_dos_clients_mount::Mount;
use interface::units::Arcsec;
use skyangle::Conversion;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 1 arcsec step on the elevation axis
    let setpoint = Signals::new(3, n_step)
        .channel(1, Signal::Constant(1f64.from_arcsec()));

    let fem   = state_space;           // Finite-element structural model
    let mount = Mount::new();          // Mount PID feedback controller
    let lom   = LinearOpticalModel::new()?; // Linear optical sensitivity

    actorscript! {
        #[labels(fem   = "GMT Structural Model",
                 mount = "Mount\nControl",
                 lom   = "Linear Optical\nModel")]

        // Feedback loop: set-point → controller → FEM → encoders → controller
        1: setpoint[MountSetPoint]
               -> mount[MountTorques]
               -> fem[MountEncoders]! -> mount

        // Structural motion → optical model
        1: fem[M1RigidBodyMotions] -> lom
        1: fem[M2RigidBodyMotions] -> lom[Arcsec<TipTilt>]~
    }

    Ok(())
}
use gmt_dos_actors::actorscript;
use gmt_dos_clients::signals::{OneSignal, Signal, Signals};
use gmt_dos_clients::smooth::{Smooth, Weight};
use gmt_dos_clients_io::{
    cfd_wind_loads::{CFDM1WindLoads, CFDM2WindLoads, CFDMountWindLoads},
    gmt_m1::M1RigidBodyMotions,
    gmt_m2::{M2RigidBodyMotions, asm::M2ASMReferenceBodyNodes},
    mount::{MountEncoders, MountSetPoint, MountTorques},
    optics::WfeRms,
};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Sigmoid envelope ramps wind loads on smoothly
    let sigmoid = OneSignal::try_from(Signals::new(1, n_step).channel(0,
        Signal::Sigmoid { amplitude: 1.0,
                          sampling_frequency_hz: 8000.0 }))?;

    let cfd_loads      = CfdLoads::foh(".", 8000).build()?;
    let fem            = state_space;
    let mount          = Mount::new();
    let lom            = LinearOpticalModel::new()?;  // M1 + M2
    let m1_lom         = LinearOpticalModel::new()?;  // M1 only
    let asm_shell_lom  = LinearOpticalModel::new()?;  // ASM shell
    let asm_rb_lom     = LinearOpticalModel::new()?;  // ASM ref-body

    actorscript! {
        #[labels(fem = "GMT FEM", mount = "Mount\nControl")]

        // Mount feedback
        1: setpoint[MountSetPoint]
               -> mount[MountTorques]
               -> fem[MountEncoders]! -> mount

        // Wind loads tapered in via sigmoid × smoother
        1: cfd_loads[CFDM1WindLoads]    -> m1_smoother
        1: sigmoid[Weight]              -> m1_smoother[CFDM1WindLoads]    -> fem
        1: cfd_loads[CFDM2WindLoads]    -> m2_smoother
        1: sigmoid[Weight]              -> m2_smoother[CFDM2WindLoads]    -> fem
        1: cfd_loads[CFDMountWindLoads] -> mount_smoother
        1: sigmoid[Weight]              -> mount_smoother[CFDMountWindLoads] -> fem

        // Stream WFE RMS to scope every 8 samples (1 kHz)
        8: lom[WfeRms<-6>]~
        1: fem[M1RigidBodyMotions] -> lom
        1: fem[M2RigidBodyMotions] -> lom

        8: m1_lom[M1RbmWfeRms]~
        1: fem[M1RigidBodyMotions] -> m1_lom

        8: asm_shell_lom[AsmShellWfeRms]~
        1: fem[M2RigidBodyMotions] -> asm_shell_lom

        8: asm_rb_lom[AsmRefBodyWfeRms]~
        1: fem[M2ASMReferenceBodyNodes] -> asm_rb_lom
    }

    Ok(())
}
use gmt_dos_actors::{actorscript, system::Sys};
use gmt_dos_clients_io::{
    cfd_wind_loads::{CFDM1WindLoads, CFDM2WindLoads, CFDMountWindLoads},
    gmt_m1::M1RigidBodyMotions,
    gmt_m2::{M2RigidBodyMotions, asm::M2ASMReferenceBodyNodes},
    optics::WfeRms,
};
use gmt_dos_clients_lom::LinearOpticalModel;
use gmt_dos_clients_servos::{
    AsmsServo, GmtServoMechanisms, WindLoads, asms_servo::ReferenceBody,
};
use gmt_dos_clients_windloads::{CfdLoads, system::{M1, M2, Mount, SigmoidCfdLoads}};

const ACTUATOR_RATE: usize = 80; // actuators run at 8000/80 = 100 Hz

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Load (or build) pre-serialised system state
    let cfd_loads = Sys::<SigmoidCfdLoads>::from_data_repo_or_else("windloads.bin", || {
        CfdLoads::foh(".", 8000).duration(5.0).windloads(&fem, Default::default())
    })?;

    let gmt_servos = Sys::<GmtServoMechanisms<ACTUATOR_RATE, 1>>::from_data_repo_or_else(
        "servos.bin",
        || GmtServoMechanisms::<ACTUATOR_RATE, 1>::new(8000.0, fem)
               .wind_loads(WindLoads::default())
               .asms_servo(AsmsServo::new().reference_body(ReferenceBody::new())),
    )?;

    // Four optical models for independent WFE contributions
    let lom          = LinearOpticalModel::new()?; // M1 + M2 combined
    let m1_lom       = LinearOpticalModel::new()?; // M1 rigid-body only
    let asm_shell_lom = LinearOpticalModel::new()?; // ASM shell
    let asm_rb_lom   = LinearOpticalModel::new()?; // ASM reference body

    actorscript! {
        // Wind loads into the servo-mechanism system (FEM + controllers)
        1: {cfd_loads::M1}[CFDM1WindLoads]       -> {gmt_servos::GmtFem}
        1: {cfd_loads::M2}[CFDM2WindLoads]       -> {gmt_servos::GmtFem}
        1: {cfd_loads::Mount}[CFDMountWindLoads] -> {gmt_servos::GmtFem}

        // Stream combined WFE to scope every 8 samples (1 kHz)
        8: lom[WfeRms<-6>]~
        1: {gmt_servos::GmtFem}[M1RigidBodyMotions] -> lom
        1: {gmt_servos::GmtFem}[M2RigidBodyMotions] -> lom

        // M1 rigid-body contribution
        8: m1_lom[M1RbmWfeRms]~
        1: {gmt_servos::GmtFem}[M1RigidBodyMotions] -> m1_lom

        // ASM shell contribution
        8: asm_shell_lom[AsmShellWfeRms]~
        1: {gmt_servos::GmtFem}[M2RigidBodyMotions] -> asm_shell_lom

        // ASM reference-body contribution
        8: asm_rb_lom[AsmRefBodyWfeRms]~
        1: {gmt_servos::GmtFem}[M2ASMReferenceBodyNodes] -> asm_rb_lom
    }

    Ok(())
}
Demo Binaries

Ready-to-run simulations

Each demo in the demos/ crate is a self-contained model of the GMT operating under realistic conditions.

step-mount
beginner
Mount Step Response

Commands a 1-arcsecond step on the elevation axis and records the tip-tilt optical response through the linear optical model.

8 kHz FEM feedback optics
View source
step-servos-mount
intermediate
Servo-Driven Mount Step

Same step test but with the full GMT servo-mechanism system instead of the bare mount controller, including all actuator dynamics.

8 kHz servos
View source
windloaded-mount
intermediate
Wind-Loaded Mount Control

CFD wind-load time series applied to the telescope structure with a sigmoid ramp-on. Streams wavefront-error RMS from four independent optical models to scope.

8 kHz CFD loads multi-rate
View source
windloaded-mount-m1
intermediate
Mount + M1 Control

Adds the M1 primary-mirror hardpoint and actuator control loops. Demonstrates multi-rate operation at 8 kHz (FEM) and 100 Hz (M1 actuators).

8 kHz 100 Hz M1 M1 ctrl
View source
windloaded-mount-m1-asms-lom
advanced
Full GMT Integrated Model

The most complete demo: Mount + M1 mirror + M2 positioners + ASM voice coils + optical model logging segment piston, tip-tilt, and WFE.

8 kHz 100 Hz ASM M1 + M2 + ASM
View source
windloaded-servos
advanced
Wind-Loaded Servo Mechanisms

Uses the high-level GmtServoMechanisms system crate — loads precomputed model state and streams four WFE metrics in parallel to scope.

servos parallel optics
View source
windloaded-servos-fsm
advanced
FSM-Enhanced Servos

Adds the Fast-Steering Mirror (FSM) subsystem to the servo model for image-stabilization loop closure at the focal plane.

FSM tip-tilt
View source
windloaded-servos-preloading
advanced
Servo Pre-loading

Demonstrates model bootstrapping: first a short bootstrap run with #[model(state = ready)], then a full simulation using steady-state initial conditions.

bootstrap state transfer
View source
Architecture

How actors connect

A simplified view of the step-mount model. Each box is an actor or display client; arrows are typed channels.

Signals setpoint MountSetPoint Mount PID controller MountTorques DiscreteModal Solver GMT FEM · 8 kHz MountEncoders ! M1RigidBody Motions M2RigidBody Motions Linear Optical Model sensitivity matrices Arcsec<TipTilt> ~ Scope live display rate: 1× forward flow feedback (!) no deadlock scope display (~) — live signal
Crate Ecosystem

Modular & composable

Every subsystem lives in its own crate. Mix and match only what your model needs.

Core Framework
gmt_dos-actorsv12

Actor runtime, channel scaffolding, and the actorscript! procedural macro.

gmt_dos-actors_dslv2

Parser and code-generator for the actorscript domain-specific language.

gmt_dos-clientsv5

Base signal sources: Signals, Smooth, Sampler, timer, and more.

gmt_dos-clients_iov4

Shared I/O type identifiers for all GMT subsystem inputs and outputs.

gmt_dos-actors-clients_interface

Extensibility interface — implement Read, Update, and Write to turn any Rust struct into a first-class actor.

Structural Dynamics
gmt_dos-clients_femv5

Discrete-time state-space solver wrapping the GMT finite-element model. Enable the cuda feature for GPU-accelerated solving via fem-cuda-solver.

gmt-femv5

Loads and processes the GMT FEM modal model from the data repository.

gmt_dos-clients_windloads

CFD-derived wind force and torque time series with first-order-hold interpolation, sourced from the GMT CFD simulations database at cfd.gmto.im.

Mirror & Mount Control
gmt_dos-clients_mount

PID-based mount controller for azimuth, elevation, and GIR axes.

gmt_dos-clients_m1-ctrl

M1 primary mirror hardpoint feedback and segment actuator force distribution.

gmt_dos-clients_m2-ctrl

M2 positioner controller, Adaptive Secondary Mirror (ASM) voice-coil driver, and Fast Steering Mirror piezo-stack actuator driver.

gmt_dos-clients_servosv3

High-level GmtServoMechanisms system combining FEM, mount, M1, M2, and ASM or FSM.

Optics & Sensing
gmt_dos-clients_lomv1

Linear Optical Model: maps rigid-body motions to wavefront error, tip-tilt, and segment piston via sensitivity matrices.

gmt_dos-clients_crseo

Full optical simulation client wrapping the crseo CUDA-accelerated library for GPU-accelerated end-to-end ray tracing and Fourier optics simulation.

gmt_dos-clients_domeseeing

Dome-seeing wavefront-error maps sourced from the GMT CFD simulations database at cfd.gmto.im.

gmt_dos-clients_optics-state

Tracks and serialises the evolving optical state of each mirror segment.

Systems
gmt_dos-systems_m1

Composite M1 system: hardpoints, actuators, and control modes pre-wired.

gmt_dos-systems_m2

Composite M2 ASMS or FSMS system with reference-body dynamics included.

gmt_dos-systems_agws

Active-Optics Wavefront Sensing system and data processing pipeline.

Tooling
gmt_dos-clients_scope

Live signal display using a client-server architecture. Actors annotated with ~ stream data to a scope client for real-time visualisation.

gmt_dos-clients_arrow

Apache Arrow / Parquet logger — attach $ to any output for zero-copy serialisation.

gmt_dos-clients_transceiver

QUIC-based transceiver for distributing actor graphs across processes or machines.