Skip to content

Commit

Permalink
better instrument generation
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Morris committed Feb 20, 2024
1 parent 58b5606 commit 6e2b4a8
Show file tree
Hide file tree
Showing 41 changed files with 1,420 additions and 437 deletions.
4 changes: 2 additions & 2 deletions maria/atmosphere/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def _simulate_atmospheric_emission(self, units="K_RJ"):
)

for band in bands:
band_index = self.instrument.dets(band=band.name).uid
band_index = self.instrument.dets(band=band.name).index

if self.verbose:
bands.set_description(f"Computing atm. emission ({band.name})")
Expand Down Expand Up @@ -167,7 +167,7 @@ def _simulate_atmospheric_emission(self, units="K_RJ"):
)

for band in bands:
band_index = self.instrument.dets(band=band.name).uid
band_index = self.instrument.dets(band=band.name).index

if self.verbose:
bands.set_description(f"Computing atm. transmission ({band.name})")
Expand Down
4 changes: 2 additions & 2 deletions maria/atmosphere/turbulent_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from .. import utils
from ..coords import Coordinates, get_center_phi_theta
from ..instrument import Instrument
from ..instrument.beam import construct_beam_filter, separably_filter
from ..instrument.beams import construct_beam_filter, separably_filter
from .weather import Weather

MIN_SAMPLES_PER_RIBBON = 2
Expand Down Expand Up @@ -309,7 +309,7 @@ def sample(self):
for band in self.instrument.bands:
# we assume the atmosphere looks the same for every nu in the band

band_index = self.instrument.dets(band=band.name).uid
band_index = self.instrument.dets(band=band.name).index
band_angular_fwhm = self.instrument.angular_fwhm(z=self.depth)[
band_index
].mean()
Expand Down
70 changes: 36 additions & 34 deletions maria/instrument/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import os
from collections.abc import Mapping
from dataclasses import dataclass, fields
from operator import attrgetter

Expand All @@ -11,9 +10,9 @@
from matplotlib.patches import Patch

from .. import utils
from .band import Band, BandList, generate_bands # noqa F401
from .beam import compute_angular_fwhm, construct_beam_filter # noqa F401
from .dets import Detectors, generate_detectors # noqa F401
from .bands import BandList # noqa F401
from .beams import compute_angular_fwhm, construct_beam_filter # noqa F401
from .dets import Detectors # noqa F401

HEX_CODE_LIST = [
mpl.colors.to_hex(mpl.colormaps.get_cmap("Paired")(t))
Expand All @@ -30,25 +29,11 @@
INSTRUMENT_CONFIGS = utils.io.read_yaml(f"{here}/instruments.yml")

INSTRUMENT_DISPLAY_COLUMNS = [
"instrument_description",
"field_of_view",
"primary_size",
"bands",
"description",
# "field_of_view",
# "primary_size",
# "bands",
]
instrument_data = pd.DataFrame(INSTRUMENT_CONFIGS).reindex(INSTRUMENT_DISPLAY_COLUMNS).T

for instrument_name, config in INSTRUMENT_CONFIGS.items():
instrument_data.at[instrument_name, "bands"] = list(config["bands"].keys())

all_instruments = list(instrument_data.index)


class InvalidInstrumentError(Exception):
def __init__(self, invalid_instrument):
super().__init__(
f"The instrument '{invalid_instrument}' is not supported."
f"Supported instruments are:\n\n{instrument_data.__repr__()}"
)


def get_instrument_config(instrument_name=None, **kwargs):
Expand All @@ -62,6 +47,9 @@ def get_instrument(instrument_name="default", **kwargs):
"""
Get an instrument from a pre-defined config.
"""
for key, config in INSTRUMENT_CONFIGS.items():
if instrument_name in config.get("aliases", []):
instrument_name = key
if instrument_name not in INSTRUMENT_CONFIGS.keys():
raise InvalidInstrumentError(instrument_name)
instrument_config = INSTRUMENT_CONFIGS[instrument_name].copy()
Expand Down Expand Up @@ -89,16 +77,11 @@ class Instrument:

@classmethod
def from_config(cls, config):
if isinstance(config.get("bands"), Mapping):
dets = Detectors.generate(
bands_config=config.pop("bands"),
field_of_view=config.get("field_of_view", 1),
geometry=config.get("geometry", "hex"),
baseline=config.get("baseline", 0),
)
dets = Detectors.from_config(config=config)

else:
raise ValueError("'bands' must be a dictionary of bands.")
for key in ["dets", "aliases"]:
if key in config:
config.pop(key)

return cls(bands=dets.bands, dets=dets, **config)

Expand Down Expand Up @@ -142,7 +125,7 @@ def baseline_y(self):

@property
def baselines(self):
return np.c_[self.baseline_x, self.baseline_y]
return np.c_[self.baseline_x, self.baseline_y, self.baseline_z]

@staticmethod
def beam_profile(r, fwhm):
Expand Down Expand Up @@ -204,11 +187,11 @@ def plot_dets(self, units="deg"):

band_color = HEX_CODE_LIST[iub]

# nom_freq = self.dets.band_center[band_mask].mean()
# band_center = self.dets.band_center[band_mask].mean()
# band_res_arcmins = 2 * self.fwhm

# 60 * np.degrees(
# 1.22 * 2.998e8 / (1e9 * nom_freq * self.primary_size)
# 1.22 * 2.998e8 / (1e9 * band_center * self.primary_size)
# )

# offsets_arcmins = 60 * np.degrees(self.offsets[band_mask])
Expand Down Expand Up @@ -240,3 +223,22 @@ def plot_dets(self, units="deg"):
ax.set_xlabel(rf"$\theta_x$ offset ({units})")
ax.set_ylabel(rf"$\theta_y$ offset ({units})")
ax.legend(handles=legend_handles)


instrument_data = pd.DataFrame(INSTRUMENT_CONFIGS).reindex(INSTRUMENT_DISPLAY_COLUMNS).T

for instrument_name, config in INSTRUMENT_CONFIGS.items():
instrument = get_instrument(instrument_name)
f_list = sorted(np.unique([band.center for band in instrument.dets.bands]))
instrument_data.at[instrument_name, "f [GHz]"] = "/".join([str(f) for f in f_list])
instrument_data.at[instrument_name, "n"] = instrument.dets.n

all_instruments = list(instrument_data.index)


class InvalidInstrumentError(Exception):
def __init__(self, invalid_instrument):
super().__init__(
f"The instrument '{invalid_instrument}' is not supported. "
f"Supported instruments are:\n\n{instrument_data.__repr__()}"
)
87 changes: 41 additions & 46 deletions maria/instrument/band.py → maria/instrument/bands.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,46 @@
import glob
import os
from dataclasses import dataclass
from typing import Sequence

import numpy as np
import pandas as pd

from .. import utils
from ..utils.io import flatten_config, read_yaml

BAND_FIELD_TYPES = {
"center": "float",
"width": "float",
"type": "str",
"passband_shape": "str",
}

here, this_filename = os.path.split(__file__)

all_bands = {}
for path in glob.glob(f"{here}/bands/*.yml"):
tag = os.path.split(path)[1].split(".")[0]
all_bands[tag] = read_yaml(path)

def generate_bands(bands_config):
bands = []

for band_key, band_config in bands_config.items():
band_name = band_config.get("band_name", band_key)
band_file = band_config.get("file")
all_bands = flatten_config(all_bands)

if band_file is not None:
if os.path.exists(band_file):
band_df = pd.read_csv(band_file)
elif os.path.exists(f"{here}/{band_file}"):
band_df = pd.read_csv(f"{here}/{band_file}")
else:
raise FileNotFoundError(band_file)

band = Band.from_passband(
name=band_name, nu=band_df.nu_GHz.values, pb=band_df.pb.values
)

else:
band = Band(
name=band_name,
center=band_config["band_center"],
width=band_config["band_width"],
white_noise=band_config.get("white_noise", 0),
pink_noise=band_config.get("pink_noise", 0),
tau=band_config.get("tau", 0),
)
class BandList(Sequence):
@classmethod
def from_config(cls, config):
bands = []

bands.append(band)
if isinstance(config, str):
config = utils.io.read_yaml(f"{here}/{config}")

return BandList(bands)
for name in config.keys():
band_config = config[name]
if "file" in band_config.keys():
band_config = utils.io.read_yaml(f'{here}/{band_config["file"]}')

bands.append(Band(name=name, **band_config))
return cls(bands=bands)

class BandList(Sequence):
def __init__(self, bands: list = []):
self.bands = bands

Expand All @@ -73,8 +65,10 @@ def __len__(self):
return len(self.bands)

def __repr__(self):
# return f"BandList([{', '.join(self.names)}])"
return self.bands.__repr__()
return self.summary.__repr__()

def __repr_html__(self):
return self.summary.__repr_html__()

def __short_repr__(self):
return f"BandList([{', '.join(self.names)}])"
Expand All @@ -100,10 +94,11 @@ class Band:
name: str
center: float
width: float
type: str = "flat"
tau: float = 0
white_noise: float = 0
pink_noise: float = 0
tau: float = 0.0
white_noise: float = 0.0
pink_noise: float = 0.0
passband_shape: str = "gaussian"
efficiency: float = 1.0

@classmethod
def from_passband(cls, name, nu, pb, pb_err=None):
Expand All @@ -112,7 +107,7 @@ def from_passband(cls, name, nu, pb, pb_err=None):
nu[pb > pb.max() / np.e**2].ptp(), 3
) # width is the two-sigma interval

band = cls(name=name, center=center, width=width, type="custom")
band = cls(name=name, center=center, width=width, passband_shape="custom")

band._nu = nu
band._pb = pb
Expand All @@ -121,20 +116,20 @@ def from_passband(cls, name, nu, pb, pb_err=None):

@property
def nu_min(self) -> float:
if self.type == "flat":
if self.passband_shape == "flat":
return self.center - 0.5 * self.width
if self.type == "gaussian":
if self.passband_shape == "gaussian":
return self.center - self.width
if self.type == "custom":
if self.passband_shape == "custom":
return self._nu[self._pb > 1e-2 * self._pb.max()].min()

@property
def nu_max(self) -> float:
if self.type == "flat":
if self.passband_shape == "flat":
return self.center + 0.5 * self.width
if self.type == "gaussian":
if self.passband_shape == "gaussian":
return self.center + self.width
if self.type == "custom":
if self.passband_shape == "custom":
return self._nu[self._pb > 1e-2 * self._pb.max()].max()

def passband(self, nu):
Expand All @@ -144,13 +139,13 @@ def passband(self, nu):

_nu = np.atleast_1d(nu)

if self.type == "gaussian":
if self.passband_shape == "gaussian":
band_sigma = self.width / 4

return np.exp(-0.5 * np.square((_nu - self.center) / band_sigma))

if self.type == "flat":
if self.passband_shape == "flat":
return np.where((_nu > self.nu_min) & (_nu < self.nu_max), 1.0, 0.0)

elif self.type == "custom":
elif self.passband_shape == "custom":
return np.interp(_nu, self._nu, self._pb)
Empty file added maria/instrument/bands.yml
Empty file.
65 changes: 65 additions & 0 deletions maria/instrument/bands/act.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
pa4:
f150:
center: 150
width: 30
passband_shape: gaussian
tau: 0
white_noise: 266.e-6
pink_noise: 1.e-1
efficiency: 0.5
f220:
center: 220
width: 30
passband_shape: gaussian
tau: 0
white_noise: 266.e-6
pink_noise: 1.e-1
efficiency: 0.5
pa5:
f090:
center: 90
width: 30
passband_shape: gaussian
tau: 0
white_noise: 266.e-6
pink_noise: 1.e-1
efficiency: 0.5
f150:
center: 150
width: 30
passband_shape: gaussian
tau: 0
white_noise: 266.e-6
pink_noise: 1.e-1
efficiency: 0.5
pa6:
f090:
center: 90
width: 30
passband_shape: gaussian
tau: 0
white_noise: 266.e-6
pink_noise: 1.e-1
efficiency: 0.5
f150:
center: 150
width: 30
passband_shape: gaussian
tau: 0
white_noise: 266.e-6
pink_noise: 1.e-1
efficiency: 0.5

f093:
n_dets: 217
field_of_view: 0.07
detector_geometry: hex

bands: [mustang2/f093]

# file: data/mustang2/passbands/f093.csv
center: 90
width: 30
efficiency: 0.5
white_noise: 266.e-6 # in K_RJ s^-1/2
pink_noise: 1.e-1 # in K_RJ s^-1/2
Loading

0 comments on commit 6e2b4a8

Please sign in to comment.