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.