Simulating observations with MUSTANG-2

MUSTANG-2 is a bolometric array on the Green Bank Telescope. In this notebook we simulate an observation of the Crab Nebula (M1).

[1]:
import maria

input_map = maria.map.get("maps/M1.h5").to("K_RJ")

input_map.data[input_map.weight < 0.2 * input_map.weight.max()] = 0

input_map.plot(slices="all")
print(input_map)
2026-08-07 14:35:01.334 INFO: Fetching https://github.com/thomaswmorris/maria-data/raw/master/maps/M1.h5
Downloading: 100%|██████████| 21.8M/21.8M [00:00<00:00, 76.8MB/s]
ProjectionMap:
  data(1, 3, 1205, 1187):
    min: -6.150e-02
    max: 5.613e-01
    units: K_RJ
    quantity: rayleigh_jeans_temperature
  stokes(1):
    components: ['I']
  nu(3):
    values: [149.8962 214.1375 272.5386] GHz
  eta(1205):
    height: 20.07’
    res: -1”
  xi(1187):
    width: 19.77’
    res: 1”
  frame: ra/dec
  center:
    ra: 05ʰ34ᵐ31.95ˢ
    dec: 22°00’52.16”
  beam(maj, min, psi): ragged
  memory: 34.33 MB
../_images/tutorials_mustang-nebula_1_2.png
[2]:
from maria import Planner

planner = Planner(target=input_map, site="green_bank", constraints={"el": (60, 90)})
plans = planner.generate_plans(total_duration=600, sample_rate=50)

plans[0].plot()
print(plans)
PlanList(1 plans, 600 s):
                           start_time duration sample_rate    target(ra,dec)     center(az,el)
chunk
0      2026-08-07 14:35:07.682 +00:00    600 s       50 Hz  (83.63°, 22.02°)  (216.3°, 70.51°)
../_images/tutorials_mustang-nebula_2_1.png
[3]:
instrument = maria.get_instrument("MUSTANG-2")

print(instrument)
instrument.plot()
Instrument(1 array)
├ arrays:
│            n field_of_view max_baseline      bands polarized primary_size
│  array1  217          4.2’          0 m  [m2/f093]     False        100 m
│
└ bands:
         name     center      width    η      NEP         NET_RJ         NET_CMB    FWHM
   0  m2/f093  86.21 GHz  20.98 GHz  0.1  15 aW√s  571.1 uK_RJ√s  690.5 uK_CMB√s  9.133”
../_images/tutorials_mustang-nebula_3_1.png
[4]:
sim = maria.Simulation(
    instrument,
    plans=plans,
    site="green_bank",
    map=input_map,
    atmosphere="2d",
)

print(sim)
Initializing observations:   0%|          | 0/1 [00:00<?, ?it/s]2026-08-07 14:35:15.269 INFO: Fetching https://github.com/thomaswmorris/maria-data/raw/master/atmosphere/spectra/am/v3/green_bank.h5

Downloading:   0%|          | 0.00/22.0M [00:00<?, ?B/s]
Downloading:  14%|█▍        | 3.08M/22.0M [00:00<00:00, 30.7MB/s]
Downloading: 100%|██████████| 22.0M/22.0M [00:00<00:00, 82.7MB/s]
2026-08-07 14:35:16.212 INFO: Fetching https://github.com/thomaswmorris/maria-data/raw/master/atmosphere/weather/era5/green_bank.h5

Downloading:   0%|          | 0.00/12.0M [00:00<?, ?B/s]
Downloading:  24%|██▎       | 2.85M/12.0M [00:00<00:00, 28.4MB/s]
Downloading: 100%|██████████| 12.0M/12.0M [00:00<00:00, 54.8MB/s]

Constructing atmosphere:   0%|          | 0/8 [00:00<?, ?it/s]
Constructing atmosphere:  25%|██▌       | 2/8 [00:00<00:00, 13.88it/s]
Constructing atmosphere:  50%|█████     | 4/8 [00:00<00:00, 13.25it/s]
Constructing atmosphere:  75%|███████▌  | 6/8 [00:00<00:00, 13.30it/s]
Constructing atmosphere: 100%|██████████| 8/8 [00:00<00:00, 13.07it/s]
Initializing observations: 100%|██████████| 1/1 [00:04<00:00,  4.27s/it]
Simulation
├ Instrument(1 array)
│ ├ arrays:
│ │            n field_of_view max_baseline      bands polarized primary_size
│ │  array1  217          4.2’          0 m  [m2/f093]     False        100 m
│ │
│ └ bands:
│          name     center      width    η      NEP         NET_RJ         NET_CMB    FWHM
│    0  m2/f093  86.21 GHz  20.98 GHz  0.1  15 aW√s  571.1 uK_RJ√s  690.5 uK_CMB√s  9.133”
├ Site:
│   region: green_bank
│   timezone: America/New_York
│   location:
│     longitude: 79°50’23.28” W
│     latitude:  38°25’59.16” N
│     altitude: 825 m
│   seasonal: True
│   diurnal: True
├ PlanList(1 plans, 600 s):
│                            start_time duration sample_rate    target(ra,dec)     center(az,el)
│ chunk
│ 0      2026-08-07 14:35:07.682 +00:00    600 s       50 Hz  (83.63°, 22.02°)  (216.3°, 70.51°)
├ Atmosphere(8 processes with 8 layers):
│ ├ spectrum:
│ │   region: green_bank
│ └ weather:
│     region: green_bank
│     altitude: 825 m
│     time: Aug 7 10:40:07 -04:00
│     pwv[mean, rms]: (28.62 mm, 858.7 um)

[5]:
tods = sim.run()
tods[0].plot()
2026-08-07 14:35:18.942 INFO: Simulating observation 1 of 1
Generating turbulence: 100%|██████████| 8/8 [00:00<00:00, 50.62it/s]
Sampling turbulence: 100%|██████████| 8/8 [00:03<00:00,  2.01it/s]
Computing atmospheric emission: 100%|██████████| 1/1 [00:00<00:00,  1.32it/s, band=m2/f093]
Sampling source 'map': 100%|██████████| 1/1 [00:06<00:00,  6.34s/it, band=m2/f093, message=Sampling channel (74 GHz, 105 GHz)]
Generating noise: 100%|██████████| 1/1 [00:00<00:00,  1.03it/s, band=m2/f093]
2026-08-07 14:35:33.212 INFO: Simulated observation 1 of 1 in 14.26 s
../_images/tutorials_mustang-nebula_5_1.png
[6]:
from maria.mappers import MaximumLikelihoodMapper

mapper = MaximumLikelihoodMapper(
    units="K_RJ",
    tods=tods,
    resolution=2 * input_map.resolution,
)

mapper.map.plot()
2026-08-07 14:35:38.143 INFO: Inferring center {'ra': '05ʰ34ᵐ32.21ˢ', 'dec': '22°00’56.31”'} for mapper
2026-08-07 14:35:38.155 INFO: Inferring mapper width 23.87’ for mapper from observation patch
2026-08-07 14:35:38.156 INFO: Inferring mapper height 23.87’ to match supplied width
2026-08-07 14:35:41.064 INFO: Inferring stokes parameters 'I' for mapper from detector sensitivities
Preprocessing TODs: 100%|██████████| 1/1 [00:02<00:00,  2.58s/it]
Computing pointing matrices: 100%|██████████| 1/1 [00:01<00:00,  1.58s/it]
../_images/tutorials_mustang-nebula_6_1.png
[7]:
mapper.fit(epochs=1, max_steps_per_epoch=100, plot=True)
Updating noise model: 100%|██████████| 1/1 [00:01<00:00,  1.58s/it, tod=1/1]
Fitting map (epoch 1/1): 100it [01:43,  1.04s/it, alpha=0.145]
../_images/tutorials_mustang-nebula_7_1.png
[8]:
from maria.mappers import compute_residual_map

residual_map = compute_residual_map(input_map.to("K_RJ")[:, 0], mapper.map)
residual_map.plot()
../_images/tutorials_mustang-nebula_8_0.png