Source code for swarmpal.experimental._local_magnetic_model
from __future__ import annotations
import chaosmagpy as cp
import pooch
from numpy import vstack
from numpy.typing import ArrayLike
from pandas import to_datetime
from xarray import DataArray
from swarmpal.io import PalProcess
LATEST_CHAOS = {
"CORE": "CHAOS-8.2_core.shc",
"CORE_EXTRAPOLATED": "CHAOS-8.2_core_extrapolated.shc",
"STATIC": "CHAOS-8.2_static.shc",
}
CHAOS_REGISTRY = {
"CHAOS-8.2_core.shc": "md5:c41b0111f14763f8722d2e54e7367c71",
"CHAOS-8.2_core_extrapolated.shc": "md5:a905f4a90ad1f6603030f64eccdeb106",
"CHAOS-8.2_static.shc": "md5:079ba5811d89d9bd66ccf0084891d689",
}
CHAOS_BASE_URL = "https://zenodo.org/records/14893049/files/"
[docs]
def fetch_chaos_file(filename):
return pooch.retrieve(
url=f"{CHAOS_BASE_URL}{filename}?download=1",
known_hash=CHAOS_REGISTRY[filename],
)
[docs]
def fetch_latest_chaos_files():
core_file = fetch_chaos_file(LATEST_CHAOS["CORE"])
core_extrapolated_file = fetch_chaos_file(LATEST_CHAOS["CORE_EXTRAPOLATED"])
static_file = fetch_chaos_file(LATEST_CHAOS["STATIC"])
return {
"core": core_file,
"core_extrapolated": core_extrapolated_file,
"static": static_file,
}
[docs]
def evaluate_chaos(
longitude: ArrayLike, latitude: ArrayLike, radius: ArrayLike, time: ArrayLike
):
chaos_files = fetch_latest_chaos_files()
model_core = cp.load_CHAOS_shcfile(chaos_files["core"])
# # TODO: Add support for extrapolated core, and static
# model_core_extrapolated = cp.load_CHAOS_shcfile(chaos_files["core_extrapolated"])
# model_static = cp.load_CHAOS_shcfile(chaos_files["static"])
# #
# Prepare inputs for ChaosMagPy
# For simplicity, fix the epoch for evaluation at the midpoint
t_mid = to_datetime(time[int(len(time) / 2)].values)
epoch_mjd = cp.data_utils.mjd2000(t_mid.year, t_mid.month, t_mid.day)
radius_km = radius / 1000
theta = 90 - latitude
phi = longitude
# Evaluate the model
B_radius, B_theta, B_phi = model_core.synth_values_tdep(
time=epoch_mjd, radius=radius_km, theta=theta, phi=phi
)
# Convert to B_NEC
return vstack((-B_theta, B_phi, -B_radius)).T
[docs]
class LocalForwardMagneticModel(PalProcess):
"""Compute a magnetic model locally and append it to a datatree"""
@property
def process_name(self):
return "LocalForwardMagneticModel"
[docs]
def set_config(self, dataset="SW_OPER_MAGA_LR_1B", model_descriptor="CHAOS-Core"):
super().set_config(
dataset=dataset, model_descriptor=model_descriptor, output_dataset=dataset
)
[docs]
def _call(self, datatree):
subtree = datatree[f"{self.config.get('dataset')}"]
ds = subtree.ds
if self.config.get("model_descriptor") == "CHAOS-Core":
B_NEC_Model = evaluate_chaos(
longitude=ds["Longitude"],
latitude=ds["Latitude"],
radius=ds["Radius"],
time=ds["Timestamp"],
)
else:
raise NotImplementedError(
f"Model descriptor {self.config.get('model_descriptor')} not implemented"
)
da = DataArray(
data=B_NEC_Model,
coords=ds["B_NEC"].coords,
dims=ds["B_NEC"].dims,
)
da.attrs = {
"units": "nT",
"description": "Locally-computed forward model of the magnetic field",
}
ds = ds.assign(
{
f"B_NEC_{self.config.get('model_descriptor')}": da,
}
)
# Assign dataset back into the datatree to return
subtree = subtree.assign(ds)
datatree[self.config.get("dataset")] = subtree
return datatree