Polarized observations¶
This tutorial covers working with polarized instrument and maps, and recovering polarized maps from observations.
We start with a normal instrument, and create two orthogonally polarized copies of each detector by setting polarized: True in the Array config:
[1]:
import maria
from maria.instrument import Band
f150 = Band(
center=150e9,
width=30e9,
NET_RJ=60e-6,
knee=1e0,
gain_error=5e-2)
array = {"field_of_view": 0.2,
"shape": "circle",
"beam_spacing": 1.8,
"primary_size": 10,
"polarized": True,
"packing": "sunflower",
"bands": [f150]}
instrument = maria.get_instrument(array=array)
print(instrument.arrays)
n field_of_view max_baseline bands polarized primary_size
array1 88 11.98’ 0 m [f150] True 10 m
We can see the resulting polarization footprint in the instrument plot:
[2]:
print(instrument)
instrument.plot()
Instrument(1 array)
├ arrays:
│ n field_of_view max_baseline bands polarized primary_size
│ array1 88 11.98’ 0 m [f150] True 10 m
│
└ bands:
name center width η NEP NET_RJ NET_CMB FWHM
0 f150 150 GHz 30 GHz 0.5 13.22 aW√s 60 uK_RJ√s 104 uK_CMB√s 52.49”
Let’s observe the use the Einstein map, which has a faint polarization signature underneath the unpolarized signal of Einstein’s face. Remember that all maps are five dimensional (stokes, frequency, time, y, x); this map has four channels in the stokes dimensions (the I, Q, U, and V Stokes parameters). We can plot all the channels by giving plot a shaped set of stokes parameters.
[3]:
input_map = maria.map.get("maps/einstein.h5")
print(input_map)
input_map.plot(slices={"stokes": [["I", "Q"],
["U", "V"]]})
2026-08-07 14:37:44.023 INFO: Fetching https://github.com/thomaswmorris/maria-data/raw/master/maps/einstein.h5
Downloading: 100%|██████████| 932k/932k [00:00<00:00, 20.0MB/s]
ProjectionMap:
data(4, 1, 685, 685):
min: -1.000e-02
max: 2.540e-01
units: K_RJ
quantity: rayleigh_jeans_temperature
stokes(4):
components: ['I' 'Q' 'U' 'V']
nu(1):
values: [90.] GHz
eta(685):
height: 60’
res: -5.263”
xi(685):
width: 60’
res: 5.263”
frame: ra/dec
center:
ra: 00ʰ00ᵐ0.00ˢ
dec: -23°00’0.00”
beam(maj, min, psi): (0 rad, 0 rad, 0 rad)
memory: 7.508 MB
[4]:
from maria import Planner
planner = Planner(target=input_map, site="mauna_kea", constraints={"el": (45, 90)})
plans = planner.generate_plans(
total_duration=1800,
max_chunk_duration=1800,
sample_rate=50,
scan_type="raster",
scan_parameters={
"n": [(15, 1), (1, 16)],
"radius": 0.5 * input_map.width.deg,
"speed": 0.5,
})
plans[0].plot()
print(plans)
PlanList(1 plans, 1800 s):
start_time duration sample_rate target(ra,dec) center(az,el)
chunk
0 2026-08-08 12:17:10.813 +00:00 1800 s 50 Hz (360°, -23°) (165.7°, 46.04°)
[5]:
sim = maria.Simulation(
instrument,
plans=plans,
site="mauna_kea",
atmosphere="2d",
map=input_map,
)
print(sim)
Initializing observations: 0%| | 0/1 [00:00<?, ?it/s]2026-08-07 14:37:55.129 INFO: Fetching https://github.com/thomaswmorris/maria-data/raw/master/atmosphere/spectra/am/v3/mauna_kea.h5
Downloading: 0%| | 0.00/22.0M [00:00<?, ?B/s]
Downloading: 13%|█▎ | 2.94M/22.0M [00:00<00:00, 29.4MB/s]
Downloading: 100%|██████████| 22.0M/22.0M [00:00<00:00, 77.5MB/s]
2026-08-07 14:37:56.565 INFO: Fetching https://github.com/thomaswmorris/maria-data/raw/master/atmosphere/weather/era5/mauna_kea.h5
Downloading: 0%| | 0.00/8.62M [00:00<?, ?B/s]
Downloading: 100%|██████████| 8.62M/8.62M [00:00<00:00, 46.0MB/s]
Constructing atmosphere: 0%| | 0/8 [00:00<?, ?it/s]
Constructing atmosphere: 12%|█▎ | 1/8 [00:04<00:30, 4.35s/it]
Constructing atmosphere: 25%|██▌ | 2/8 [00:10<00:31, 5.20s/it]
Constructing atmosphere: 38%|███▊ | 3/8 [00:15<00:26, 5.24s/it]
Constructing atmosphere: 50%|█████ | 4/8 [00:21<00:22, 5.52s/it]
Constructing atmosphere: 62%|██████▎ | 5/8 [00:26<00:16, 5.34s/it]
Constructing atmosphere: 75%|███████▌ | 6/8 [00:31<00:10, 5.34s/it]
Constructing atmosphere: 88%|████████▊ | 7/8 [00:36<00:05, 5.19s/it]
Constructing atmosphere: 100%|██████████| 8/8 [00:40<00:00, 5.09s/it]
Initializing observations: 100%|██████████| 1/1 [00:44<00:00, 44.60s/it]
Simulation
├ Instrument(1 array)
│ ├ arrays:
│ │ n field_of_view max_baseline bands polarized primary_size
│ │ array1 88 11.98’ 0 m [f150] True 10 m
│ │
│ └ bands:
│ name center width η NEP NET_RJ NET_CMB FWHM
│ 0 f150 150 GHz 30 GHz 0.5 13.22 aW√s 60 uK_RJ√s 104 uK_CMB√s 52.49”
├ Site:
│ region: mauna_kea
│ timezone: Pacific/Honolulu
│ location:
│ longitude: 155°28’37.20” W
│ latitude: 19°49’22.08” N
│ altitude: 4.092 km
│ seasonal: True
│ diurnal: True
├ PlanList(1 plans, 1800 s):
│ start_time duration sample_rate target(ra,dec) center(az,el)
│ chunk
│ 0 2026-08-08 12:17:10.813 +00:00 1800 s 50 Hz (360°, -23°) (165.7°, 46.04°)
├ Atmosphere(8 processes with 8 layers):
│ ├ spectrum:
│ │ region: mauna_kea
│ └ weather:
│ region: mauna_kea
│ altitude: 4.092 km
│ time: Aug 8 02:32:10 -10:00
│ pwv[mean, rms]: (2.479 mm, 74.37 um)
[6]:
tods = sim.run()
print(tods)
tods[0].plot()
2026-08-07 14:38:39.239 INFO: Simulating observation 1 of 1
Generating turbulence: 100%|██████████| 8/8 [00:02<00:00, 2.90it/s]
Sampling turbulence: 100%|██████████| 8/8 [00:04<00:00, 1.63it/s]
Computing atmospheric emission: 100%|██████████| 1/1 [00:01<00:00, 1.07s/it, band=f150]
Sampling source 'map': 100%|██████████| 1/1 [00:06<00:00, 6.64s/it, band=f150, message=Sampling channel (105 GHz, 195 GHz)]
Generating noise: 100%|██████████| 1/1 [00:00<00:00, 1.05it/s, band=f150]
2026-08-07 14:38:57.943 INFO: Simulated observation 1 of 1 in 18.69 s
[TOD(shape=(88, 90000), fields=['atmosphere', 'map', 'noise'], units='uK_RJ', start=2026-08-08 12:47:10.792 +00:00, duration=1800.0s, sample_rate=50.0Hz, metadata={'atmosphere': True, 'sim_time': <Arrow [2026-08-07T14:38:55.964364+00:00]>, 'altitude': 4092.0, 'region': 'mauna_kea', 'pwv': 2.479, 'base_temperature': 279.693})]
[7]:
from maria.mapping import MaximumLikelihoodMapper
mapper = MaximumLikelihoodMapper(
frame="ra/dec",
resolution=2 * input_map.resolution.deg,
tod_preprocessing={
"remove_spline": {"knot_spacing": 300, "remove_el_gradient_order": 1},
},
tods=tods,
bilinear=False,
)
mapper.map.plot(slices="all")
2026-08-07 14:39:03.409 INFO: Inferring center {'ra': '23ʰ59ᵐ59.39ˢ', 'dec': '-22°59’58.16”'} for mapper
2026-08-07 14:39:03.422 INFO: Inferring mapper width 1.197° for mapper from observation patch
2026-08-07 14:39:03.422 INFO: Inferring mapper height 1.197° to match supplied width
2026-08-07 14:39:06.909 INFO: Inferring stokes parameters 'IQU' for mapper from detector sensitivities
Preprocessing TODs: 100%|██████████| 1/1 [00:03<00:00, 3.97s/it]
Computing pointing matrices: 100%|██████████| 1/1 [00:03<00:00, 3.84s/it]
[8]:
mapper.fit(epochs=2,
steps_per_epoch=25,
plot=True,
plot_kwargs={
"slices": "all",
"center_zero": True,
"contrast": 1e-3})
Updating noise model: 100%|██████████| 1/1 [00:05<00:00, 5.54s/it, tod=1/1]
Fitting map (epoch 1/2): 248it [14:31, 3.76s/it, alpha=0.0155]2026-08-07 14:54:04.604 INFO: Finished conjugate gradient descent, terminating
Fitting map (epoch 1/2): 248it [14:35, 3.53s/it, alpha=0.0155]
Updating noise model: 100%|██████████| 1/1 [00:05<00:00, 5.87s/it, tod=1/1]
Fitting map (epoch 2/2): 261it [15:00, 3.65s/it, alpha=0.00512]2026-08-07 15:09:16.914 INFO: Finished conjugate gradient descent, terminating
Fitting map (epoch 2/2): 261it [15:04, 3.47s/it, alpha=0.00512]
Note that we can’t see any of the circular polarization, since our instrument isn’t sensitive to it.