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
f090 = Band(
center=90e9, # in Hz
width=20e9, # in Hz
NET_RJ=40e-6, # in K sqrt(s)
knee=1e0, # in Hz
gain_error=5e-2)
f150 = Band(
center=150e9,
width=30e9,
NET_RJ=60e-6,
knee=1e0,
gain_error=5e-2)
array = {"field_of_view": 0.5,
"shape": "circle",
"beam_spacing": 1.5,
"primary_size": 10,
"polarized": True,
"bands": [f090, f150]}
instrument = maria.get_instrument(array=array)
print(instrument.arrays)
n FOV baseline bands polarized
array1 652 0.478° 0 m [f090,f150] True
We can see the resulting polarization footprint in the instrument plot:
[2]:
print(instrument)
instrument.plot()
Instrument(1 array)
├ arrays:
│ n FOV baseline bands polarized
│ array1 652 0.478° 0 m [f090,f150] True
│
└ bands:
name center width η NEP NET_RJ NET_CMB FWHM
0 f090 90 GHz 20 GHz 0.5 5.445 aW√s 40 uK_RJ√s 49.13 uK_CMB√s 1.458’
1 f150 150 GHz 30 GHz 0.5 12.25 aW√s 60 uK_RJ√s 104 uK_CMB√s 0.8748’

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]:
from maria.io import fetch
input_map = maria.map.load(fetch("maps/einstein.h5"))
input_map.plot(stokes=[["I", "Q"],
["U", "V"]])
Downloading https://github.com/thomaswmorris/maria-data/raw/master/maps/einstein.h5: 100%|████████████████| 931k/931k [00:00<00:00, 18.2MB/s]

[4]:
from maria import Planner
planner = Planner(target=input_map, site="llano_de_chajnantor", constraints={"el": (60, 90)})
plans = planner.generate_plans(total_duration=900, # in seconds
sample_rate=50) # in Hz
plans[0].plot()
print(plans)
PlanList(2 plans, 900 s):
start_time duration target(ra,dec) center(az,el)
chunk
0 2025-08-25 04:07:06.429 +00:00 600 s (360°, -23.01°) (95.94°, 61.17°)
1 2025-08-25 04:17:43.929 +00:00 300 s (0.8537’, -23.05°) (95.57°, 63.02°)

[5]:
sim = maria.Simulation(
instrument,
plans=plans,
site="llano_de_chajnantor",
atmosphere="2d",
atmosphere_kwargs={"weather": {"pwv": 0.5}},
map=input_map
)
print(sim)
Simulation
├ Instrument(1 array)
│ ├ arrays:
│ │ n FOV baseline bands polarized
│ │ array1 652 0.478° 0 m [f090,f150] True
│ │
│ └ bands:
│ name center width η NEP NET_RJ NET_CMB FWHM
│ 0 f090 90 GHz 20 GHz 0.5 5.445 aW√s 40 uK_RJ√s 49.13 uK_CMB√s 1.458’
│ 1 f150 150 GHz 30 GHz 0.5 12.25 aW√s 60 uK_RJ√s 104 uK_CMB√s 0.8748’
├ Site:
│ region: chajnantor
│ timezone: America/Santiago
│ location:
│ longitude: 67°45’17.28” W
│ latitude: 23°01’45.84” S
│ altitude: 5064 m
│ seasonal: True
│ diurnal: True
├ PlanList(2 plans, 900 s):
│ start_time duration target(ra,dec) center(az,el)
│ chunk
│ 0 2025-08-25 04:07:06.429 +00:00 600 s (360°, -23.01°) (95.94°, 61.17°)
│ 1 2025-08-25 04:17:43.929 +00:00 300 s (0.8537’, -23.05°) (95.57°, 63.02°)
├ '2d'
└ ProjectedMap:
shape(stokes, nu, y, x): (4, 1, 685, 685)
stokes: IQUV
nu: [90.] GHz
t: naive
z: naive
quantity: rayleigh_jeans_temperature
units: K_RJ
min: -1.000e-02
max: 2.540e-01
center:
ra: 00ʰ00ᵐ0.00ˢ
dec: -23°00’0.00”
size(y, x): (1°, 1°)
resolution(y, x): (5.255”, 5.255”)
beam(maj, min, rot): (0 rad, 0 rad, 0 rad)
memory: 30.03 MB
[6]:
tods = sim.run()
print(tods)
tods[0].plot()
2025-08-24 19:02:53.362 INFO: Simulating observation 1 of 2
Constructing atmosphere: 100%|████████████████| 10/10 [00:43<00:00, 4.35s/it]
Generating turbulence: 100%|████████████████| 10/10 [00:00<00:00, 16.24it/s]
Sampling turbulence: 100%|████████████████| 10/10 [00:09<00:00, 1.07it/s]
Computing atmospheric emission: 100%|████████████████| 2/2 [00:02<00:00, 1.16s/it, band=f150]
Sampling map: 100%|████████████████| 2/2 [00:08<00:00, 4.48s/it, band=f150, channel=(0 Hz, inf Hz), stokes=V]
Generating noise: 100%|████████████████| 2/2 [00:00<00:00, 2.37it/s, band=f150]
2025-08-24 19:04:12.228 INFO: Simulated observation 1 of 2 in 78.85 s
2025-08-24 19:04:12.229 INFO: Simulating observation 2 of 2
Constructing atmosphere: 100%|████████████████| 10/10 [00:17<00:00, 1.79s/it]
Generating turbulence: 100%|████████████████| 10/10 [00:00<00:00, 27.07it/s]
Sampling turbulence: 100%|████████████████| 10/10 [00:06<00:00, 1.48it/s]
Computing atmospheric emission: 100%|████████████████| 2/2 [00:01<00:00, 1.27it/s, band=f150]
Sampling map: 100%|████████████████| 2/2 [00:04<00:00, 2.50s/it, band=f150, channel=(0 Hz, inf Hz), stokes=V]
Generating noise: 100%|████████████████| 2/2 [00:00<00:00, 4.18it/s, band=f150]
2025-08-24 19:04:51.224 INFO: Simulated observation 2 of 2 in 38.98 s
[TOD(shape=(652, 30000), fields=['atmosphere', 'map', 'noise'], units='K_RJ', start=2025-08-25 04:17:06.409 +00:00, duration=600.0s, sample_rate=50.0Hz, metadata={'atmosphere': True, 'sim_time': <Arrow [2025-08-24T19:04:01.938143+00:00]>, 'altitude': 5064.0, 'region': 'chajnantor', 'pwv': 0.5, 'base_temperature': 272.51}), TOD(shape=(652, 15000), fields=['atmosphere', 'map', 'noise'], units='K_RJ', start=2025-08-25 04:22:43.909 +00:00, duration=300.0s, sample_rate=50.0Hz, metadata={'atmosphere': True, 'sim_time': <Arrow [2025-08-24T19:04:46.695869+00:00]>, 'altitude': 5064.0, 'region': 'chajnantor', 'pwv': 0.871, 'base_temperature': 272.495})]

[7]:
tods
[7]:
[TOD(shape=(652, 30000), fields=['atmosphere', 'map', 'noise'], units='K_RJ', start=2025-08-25 04:17:06.409 +00:00, duration=600.0s, sample_rate=50.0Hz, metadata={'atmosphere': True, 'sim_time': <Arrow [2025-08-24T19:04:01.938143+00:00]>, 'altitude': 5064.0, 'region': 'chajnantor', 'pwv': 0.5, 'base_temperature': 272.51}),
TOD(shape=(652, 15000), fields=['atmosphere', 'map', 'noise'], units='K_RJ', start=2025-08-25 04:22:43.909 +00:00, duration=300.0s, sample_rate=50.0Hz, metadata={'atmosphere': True, 'sim_time': <Arrow [2025-08-24T19:04:46.695869+00:00]>, 'altitude': 5064.0, 'region': 'chajnantor', 'pwv': 0.871, 'base_temperature': 272.495})]
[8]:
from maria.mappers import BinMapper
mapper = BinMapper(
center=(0, -23),
stokes="IQU",
frame="ra/dec",
width=1.0,
height=1.0,
resolution=1.0 / 256,
tod_preprocessing={
"window": {"name": "tukey", "kwargs": {"alpha": 0.1}},
"remove_spline": {"knot_spacing": 30, "remove_el_gradient": True},
"remove_modes": {"modes_to_remove": [0]},
},
map_postprocessing={
"gaussian_filter": {"sigma": 1},
# "median_filter": {"size": 1},
},
units="mK_RJ",
)
mapper.add_tods(tods)
output_map = mapper.run()
print(output_map)
Mapping band f090: 100%|██████████| 2/2 [00:05<00:00, 2.62s/it, stokes=U, tod=2/2]
2025-08-24 19:05:03.511 INFO: Ran mapper for band f090 in 5.236 s.
Mapping band f150: 100%|██████████| 2/2 [00:05<00:00, 2.59s/it, stokes=U, tod=2/2]
2025-08-24 19:05:08.702 INFO: Ran mapper for band f150 in 5.179 s.
ProjectedMap:
shape(stokes, nu, y, x): (3, 2, 256, 256)
stokes: IQU
nu: [ 90. 150.] GHz
t: naive
z: naive
quantity: rayleigh_jeans_temperature
units: mK_RJ
min: -2.336e+02
max: 1.987e+02
center:
ra: 00ʰ00ᵐ0.00ˢ
dec: -23°00’0.00”
size(y, x): (1°, 1°)
resolution(y, x): (14.06”, 14.06”)
beam(maj, min, rot): (0 rad, 0 rad, 0 rad)
memory: 6.291 MB
Note that we can’t see any of the circular polarization, since our instrument isn’t sensitive to it.
[9]:
output_map.plot(stokes=["I", "Q", "U"], nu_index=[[0], [1]])

[ ]: