Skip to content

Commit

Permalink
Merge pull request #43 from thomaswmorris/refactor
Browse files Browse the repository at this point in the history
Refactor to fix bugs and other problems
  • Loading branch information
thomaswmorris authored Dec 20, 2023
2 parents 917d49f + 381d641 commit 8f26c6d
Show file tree
Hide file tree
Showing 78 changed files with 1,902 additions and 953 deletions.
107 changes: 107 additions & 0 deletions Untitled.ipynb

Large diffs are not rendered by default.

50 changes: 43 additions & 7 deletions docs/source/tutorials/Synthetic_AtLAST_observations.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -21,9 +21,21 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 2,
"metadata": {},
"outputs": [],
"outputs": [
{
"ename": "ImportError",
"evalue": "cannot import name 'mappers' from 'maria' (/Users/tom/repos/maria/maria/__init__.py)",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[2], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mmaria\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m Simulation\n\u001b[0;32m----> 2\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mmaria\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m mappers\n",
"\u001b[0;31mImportError\u001b[0m: cannot import name 'mappers' from 'maria' (/Users/tom/repos/maria/maria/__init__.py)"
]
}
],
"source": [
"from maria import Simulation\n",
"from maria import mappers"
Expand All @@ -46,9 +58,21 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 3,
"metadata": {},
"outputs": [],
"outputs": [
{
"ename": "NameError",
"evalue": "name 'map_file' is not defined",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[3], line 8\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[38;5;66;03m# - Input figure\u001b[39;00m\n\u001b[1;32m 7\u001b[0m cmap \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mRdBu_r\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m----> 8\u001b[0m inputfile \u001b[38;5;241m=\u001b[39m \u001b[43mmap_file\u001b[49m\n\u001b[1;32m 10\u001b[0m hdu \u001b[38;5;241m=\u001b[39m fits\u001b[38;5;241m.\u001b[39mopen(inputfile)\n\u001b[1;32m 11\u001b[0m hdu[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mdata \u001b[38;5;241m=\u001b[39m hdu[\u001b[38;5;241m0\u001b[39m]\u001b[38;5;241m.\u001b[39mdata\n",
"\u001b[0;31mNameError\u001b[0m: name 'map_file' is not defined"
]
}
],
"source": [
"from astropy.io import fits\n",
"from astropy.wcs import WCS\n",
Expand Down Expand Up @@ -89,9 +113,21 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 4,
"metadata": {},
"outputs": [],
"outputs": [
{
"ename": "NameError",
"evalue": "name 'map_file' is not defined",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[4], line 11\u001b[0m\n\u001b[1;32m 1\u001b[0m sim \u001b[38;5;241m=\u001b[39m Simulation(\n\u001b[1;32m 2\u001b[0m \u001b[38;5;66;03m# Mandatory minimal weither settings\u001b[39;00m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;66;03m# ---------------------\u001b[39;00m\n\u001b[1;32m 4\u001b[0m array\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mAtLAST\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;66;03m# Array type\u001b[39;00m\n\u001b[1;32m 5\u001b[0m pointing\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdaisy\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;66;03m# Scanning strategy\u001b[39;00m\n\u001b[1;32m 6\u001b[0m site\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mAPEX\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;66;03m# Site\u001b[39;00m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;66;03m# atm_model = 'single_layer', # The atmospheric model, set to None if you want a noiseless observation.\u001b[39;00m\n\u001b[1;32m 8\u001b[0m atm_model\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mNone\u001b[39;00m, \u001b[38;5;66;03m# The atmospheric model, set to None if you want a noiseless observation.\u001b[39;00m\n\u001b[1;32m 9\u001b[0m \u001b[38;5;66;03m# True sky input\u001b[39;00m\n\u001b[1;32m 10\u001b[0m \u001b[38;5;66;03m# ---------------------\u001b[39;00m\n\u001b[0;32m---> 11\u001b[0m map_file\u001b[38;5;241m=\u001b[39m\u001b[43mmap_file\u001b[49m, \u001b[38;5;66;03m# Input files must be a fits file.\u001b[39;00m\n\u001b[1;32m 12\u001b[0m \u001b[38;5;66;03m# map_file can also be set to None if are only interested in the noise\u001b[39;00m\n\u001b[1;32m 13\u001b[0m scan_center\u001b[38;5;241m=\u001b[39mmap_center,\n\u001b[1;32m 14\u001b[0m map_center\u001b[38;5;241m=\u001b[39mmap_center, \u001b[38;5;66;03m# RA & Dec in degree \u001b[39;00m\n\u001b[1;32m 15\u001b[0m integration_time \u001b[38;5;241m=\u001b[39m integration_time,\n\u001b[1;32m 16\u001b[0m \u001b[38;5;66;03m# Defeault Observational setup\u001b[39;00m\n\u001b[1;32m 17\u001b[0m \u001b[38;5;66;03m# ----------------------------\u001b[39;00m\n\u001b[1;32m 18\u001b[0m pointing_frame\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mra_dec\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;66;03m# frame\u001b[39;00m\n\u001b[1;32m 19\u001b[0m field_of_view\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m2.0\u001b[39m,\n\u001b[1;32m 20\u001b[0m dets\u001b[38;5;241m=\u001b[39m{\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mf090\u001b[39m\u001b[38;5;124m\"\u001b[39m: {\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mn\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;241m2000\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mband_center\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;241m92.0\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mband_width\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;241m52.0\u001b[39m}},\n\u001b[1;32m 21\u001b[0m start_time\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m2022-08-10T06:00:00\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m 22\u001b[0m scan_options\u001b[38;5;241m=\u001b[39m{\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mspeed\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;241m1\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mradius\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;241m1.3\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mpetals\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;241m2.11\u001b[39m},\n\u001b[1;32m 23\u001b[0m sample_rate\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m100.0\u001b[39m,\n\u001b[1;32m 24\u001b[0m \u001b[38;5;66;03m# Additional inputs:\u001b[39;00m\n\u001b[1;32m 25\u001b[0m \u001b[38;5;66;03m# ----------------------\u001b[39;00m\n\u001b[1;32m 26\u001b[0m weather_quantiles\u001b[38;5;241m=\u001b[39m{\n\u001b[1;32m 27\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mcolumn_water_vapor\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;241m0.1\u001b[39m\n\u001b[1;32m 28\u001b[0m }, \u001b[38;5;66;03m# Weather conditions specific for that site\u001b[39;00m\n\u001b[1;32m 29\u001b[0m map_units\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mJy/pixel\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;66;03m# Kelvin Rayleigh Jeans (K, defeault) or Jy/pixel\u001b[39;00m\n\u001b[1;32m 30\u001b[0m map_res\u001b[38;5;241m=\u001b[39mpixel_size, \u001b[38;5;66;03m# degree, overwrites header information\u001b[39;00m\n\u001b[1;32m 31\u001b[0m )\n",
"\u001b[0;31mNameError\u001b[0m: name 'map_file' is not defined"
]
}
],
"source": [
"sim = Simulation(\n",
" # Mandatory minimal weither settings\n",
Expand Down
352 changes: 290 additions & 62 deletions docs/source/tutorials/Synthetic_MUSTANG2_observations.ipynb

Large diffs are not rendered by default.

30 changes: 17 additions & 13 deletions maria/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@

ARRAY_CONFIGS = utils.io.read_yaml(f"{here}/configs/arrays.yml")

DISPLAY_COLUMNS = ["description", "field_of_view", "primary_size", "bands"]
DISPLAY_COLUMNS = ["array_description", "field_of_view", "primary_size", "bands"]
supported_arrays_table = pd.DataFrame(ARRAY_CONFIGS).T

band_lists = []
for array_name, config in ARRAY_CONFIGS.items():
band_lists.append(
"/".join(
[str(band_config["band_center"]) for band_config in config["dets"].values()]
[
str(band_config["band_center"])
for band_config in config["detector_config"].values()
]
)
)
supported_arrays_table.loc[:, "bands"] = band_lists
Expand Down Expand Up @@ -97,19 +100,20 @@ def get_array_config(array_name=None, **kwargs):
if array_name not in ARRAY_CONFIGS.keys():
raise InvalidArrayError(array_name)
array_config = ARRAY_CONFIGS[array_name].copy()
for k, v in kwargs.items():
if k in all_array_params.keys():
array_config[k] = v
for key, value in kwargs.items():
if key in all_array_params.keys():
array_config[key] = value
else:
raise ValueError(f"'{k}' is not a valid argument for an array!")
raise ValueError(f"'{key}' is not a valid argument for an array!")
return array_config


def get_array(array_name="default", **kwargs):
"""
Get an array from a pre-defined config.
"""
return Array.from_config(get_array_config(array_name, **kwargs))
array_config = get_array_config(array_name=array_name, **kwargs)
return Array.from_config(array_config)


REQUIRED_DET_CONFIG_KEYS = ["n", "band_center", "band_width"]
Expand Down Expand Up @@ -177,7 +181,7 @@ class Array:
An array.
"""

description: str = ""
array_description: str = ""
primary_size: float = 5 # in meters
field_of_view: float = 1 # in deg
geometry: str = "hex"
Expand All @@ -188,7 +192,7 @@ class Array:
max_el_acc: float = np.inf # in deg/s^2
az_bounds: Tuple[float, float] = (0, 360) # in degrees
el_bounds: Tuple[float, float] = (0, 90) # in degrees
documentation: str = ""
array_documentation: str = ""
dets: pd.DataFrame = None # dets, it's complicated

def __repr__(self):
Expand Down Expand Up @@ -229,19 +233,19 @@ def baselines(self):

@classmethod
def from_config(cls, config):
if isinstance(config["dets"], Mapping):
if isinstance(config["detector_config"], Mapping):
field_of_view = config.get("field_of_view", 1)
geometry = config.get("geometry", "hex")
baseline = config.get("baseline", 0) # default to zero baseline
dets = generate_dets_from_config(
config["dets"],
bands=config["detector_config"],
field_of_view=field_of_view,
geometry=geometry,
baseline=baseline,
)

return cls(
description=config["description"],
array_description=config["array_description"],
primary_size=config["primary_size"],
field_of_view=field_of_view,
baseline=baseline,
Expand All @@ -252,7 +256,7 @@ def from_config(cls, config):
max_el_acc=config["max_el_acc"],
az_bounds=tuple(config["az_bounds"]),
el_bounds=tuple(config["el_bounds"]),
documentation=config["documentation"],
array_documentation=config["array_documentation"],
dets=dets,
)

Expand Down
200 changes: 196 additions & 4 deletions maria/atmosphere/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,207 @@
from .linear_angular import LinearAngularSimulation # noqa F401
from .single_layer import SingleLayerSimulation # noqa F401
# from .linear_angular import LinearAngularSimulation # noqa F401
import os
import time as ttime

# how do we do the bands? this is a great question.
# because all practical telescope instrumentation assume a constant band
import h5py
import numpy as np
import scipy as sp

# from .kolmogorov_taylor import KolmogorovTaylorSimulation
from tqdm import tqdm

from .. import utils
from ..base import BaseSimulation
from .turbulent_layer import TurbulentLayer # noqa F401

# how do we do the bands? this is a great question.
# because all practical telescope instrumentation assume a constant band


ATMOSPHERE_PARAMS = {
"min_depth": 500,
"max_depth": 3000,
"n_layers": 4,
"min_beam_res": 4,
"turbulent_outer_scale": 500,
}


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


class AtmosphericSpectrum:
def __init__(self, filepath):
"""
A dataclass to hold spectra as attributes
"""
with h5py.File(filepath, "r") as f:
self.side_nu_GHz = f["side_nu_GHz"][:].astype(float)
self.side_elevation_deg = f["side_elevation_deg"][:].astype(float)
self.side_line_of_sight_pwv_mm = f["side_line_of_sight_pwv_mm"][:].astype(
float
)
self.temperature_rayleigh_jeans_K = f["temperature_rayleigh_jeans_K"][
:
].astype(float)
self.phase_delay_um = f["phase_delay_um"][:].astype(float)


class AtmosphereMixin:
def _initialize_atmosphere(self):
"""
This assume that BaseSimulation.__init__() has been called.
"""

print("Initializing atmosphere")

utils.validate_pointing(self.det_coords.az, self.det_coords.el)

# self.min_atmosphere_height = min_atmosphere_height
# self.max_atmosphere_height = max_atmosphere_height

self._spectrum_filepath = f"{here}/spectra/{self.site.region}.h5"
self.spectrum = (
AtmosphericSpectrum(filepath=self._spectrum_filepath)
if os.path.exists(self._spectrum_filepath)
else None
)

if self.atmosphere_model == "2d":
self.n_layers = 6

self.layer_depths = np.linspace(500, 3000, self.n_layers)
self.layers = []

for layer_index, layer_depth in enumerate(self.layer_depths):
layer = TurbulentLayer(
array=self.array,
boresight=self.boresight,
depth=layer_depth,
weather=self.weather,
)

self.layers.append(layer)

if self.atmosphere_model == "3d":
self.initialize_3d_atmosphere()

def _simulate_atmospheric_fluctuations(self):
if self.atmosphere_model == "2d":
self._simulate_2d_atmospheric_fluctuations()

if self.atmosphere_model == "3d":
self._simulate_3d_atmospheric_fluctuations()

def _simulate_2d_turbulence(self):
"""
Simulate layers of two-dimensional turbulence.
"""

layer_data = np.zeros((self.n_layers, self.array.n_dets, self.pointing.n_time))

for layer_index, layer in enumerate(self.layers):
layer.generate()
layer_data[layer_index] = layer.sample()

return layer_data

def _simulate_2d_atmospheric_fluctuations(self):
"""
Simulate layers of two-dimensional turbulence.
"""

turbulence = self._simulate_2d_turbulence()

rel_layer_scaling = np.interp(
self.site.altitude + self.layer_depths[:, None, None] * np.sin(self.EL),
self.weather.altitude_levels,
self.weather.absolute_humidity,
)
rel_layer_scaling /= np.sqrt(np.square(rel_layer_scaling).sum(axis=0)[None])

self.layer_scaling = self.pwv_rms_frac * self.weather.pwv * rel_layer_scaling

self.line_of_sight_pwv = (
self.weather.pwv + (self.layer_scaling * turbulence).sum(axis=0)
) / np.sin(self.EL)

# layer_boundaries = np.linspace(self.min_layer_height, self.max_layer_height, n_layers + 1)
# self.layer_heights = 0.5 * (layer_boundaries[1:] + layer_boundaries[:-1])
# self.n_layers = n_layers

# self.compute_autoregression_boundaries_2d(layer_depth)
# self.min_beam_res = min_atmosphere_beam_res

return

# def _simulate_integrated_water_vapor(self):
# detector_values = self.simulate_normalized_effective_water_vapor()

# # this is "zenith-scaled"
# self.line_of_sight_pwv = (
# self.weather.pwv
# * (1.0 + self.pwv_rms_frac * detector_values)
# / np.sin(self.EL)
# )

@property
def EL(self):
return utils.coords.dx_dy_to_phi_theta(
self.array.offset_x[:, None],
self.array.offset_y[:, None],
self.boresight.az,
self.boresight.el,
)[1]

def _simulate_atmospheric_emission(self, units="K_RJ"):
start_time = ttime.monotonic()

if units == "K_RJ": # Kelvin Rayleigh-Jeans
self._simulate_atmospheric_fluctuations()
self.data["atmosphere"] = np.empty(
(self.array.n_dets, self.pointing.n_time), dtype=np.float32
)

for uband in tqdm(self.array.ubands, desc="Sampling atmosphere"):
# for uband in self.array.ubands:
band_mask = self.array.dets.band.values == uband

det_nu_samples = np.linspace(
self.array.band_min[band_mask], self.array.band_max[band_mask], 64
).mean(axis=-1)

det_temperature_grid = sp.interpolate.interp1d(
self.spectrum.side_nu_GHz,
self.spectrum.temperature_rayleigh_jeans_K,
axis=-1,
)(det_nu_samples).mean(axis=-1)

band_T_RJ_interpolator = sp.interpolate.RegularGridInterpolator(
(
self.spectrum.side_line_of_sight_pwv_mm,
self.spectrum.side_elevation_deg,
),
det_temperature_grid,
)

self.data["atmosphere"][band_mask] = band_T_RJ_interpolator(
(
self.line_of_sight_pwv[band_mask],
np.degrees(self.det_coords.el[band_mask]),
)
)

if units == "F_RJ": # Fahrenheit Rayleigh-Jeans 🇺🇸
self._simulate_atmospheric_emission(self, units="K_RJ")
self.data["atmosphere"] = 1.8 * (self.data["atmosphere"] - 273.15) + 32

if self.verbose:
print(
f"ran atmospheric simulation in {ttime.monotonic() - start_time:.01f} seconds"
)


class AtmosphereSimulation(BaseSimulation, AtmosphereMixin):
def __init__(self, *args, **kwargs):
super(AtmosphereSimulation, self).__init__(*args, **kwargs)
self._initialize_atmosphere()
Loading

0 comments on commit 8f26c6d

Please sign in to comment.