Source code for solarwindpy.core.spacecraft

#!/usr/bin/env python
r"""Contais :py:class:`~solarwindpy.core.spacecraft.Spacecraft` class.

Class inherets from :py:class:`~solarwindpy.core.base.Base` and contains :py:class:`~solarwindpy.core.vector.Vector` objects.
"""

import pandas as pd
import numpy as np

# We rely on views via DataFrame.xs to reduce memory size and do not
# `.copy(deep=True)`, so we want to make sure that this doesn't
# accidentally cause a problem.

from . import base
from . import vector


[docs] class Spacecraft(base.Base): r"""Representation of a spacecraft trajectory. Parameters ---------- data : :class:`pandas.DataFrame` Vector position (and optionally velocity) with MultiIndex columns ``("M", "C")``. name : str Identifier of the spacecraft. frame : str Reference frame of the vectors, e.g. ``"HCI"`` or ``"GSE"``. """
[docs] def __init__(self, data, name, frame): r"""Initialize a spacecraft with `data`. Parameters ---------- data: pd.DataFrame 2-level MultiIndex columns identifying measurment "M" and component "C". Should contain at a minimum vector position. Can also contain vector velocity and Carrington location. If vector velocity included, it should be in the same frame of reference as the position. name: str Identify the spacecraft, e.g. Parker Solar Probe (PSP) or Wind. Internally stored in all caps for consistency. frame: str The frame of reference for the spacecraft position and velocity, e.g. Geocentric Solar Ecliptic (GSE) or Heliocentric Internal (HCI). Examples -------- >>> epoch = pd.Series({0: pd.to_datetime("1995-01-01"), ... 1: pd.to_datetime("2015-03-23"), ... 2: pd.to_datetime("2022-10-09")}, name="Epoch") >>> data = {("pos", "x", ""): {0: -42, 1: -22, 2: -34}, ... ("pos", "y", ""): {0: 23, 1: 31, 2: 11}, ... ("pos", "z", ""): {0: 35, 1: 27, 2: 49}, ... ("v", "x", ""): {0: 9.0, 1: 10.0, 2: 8.0}, ... ("v", "y", ""): {0: -80.0, 1: -70.0, 2: -90.0}, ... ("v", "z", ""): {0: -0.5, 1: 0.5, 2: 1.5}, ... ("carr", "lat", ""): {0: -2.0, 1: -1.0, 2: 3.0}, ... ("carr", "lon", ""): {0: -26.0, 1: -36.0, 2: -16.0}} >>> spacecraft = pd.DataFrame.from_dict(data, ... orient="columns", ... dtype=np.float64) >>> spacecraft.index = epoch >>> spacecraft.columns.names = ["M", "C", "S"] >>> spacecraft = spacecraft.xs("", axis=1, level="S") >>> spacecraft # doctest: +NORMALIZE_WHITESPACE M pos v carr C x y z x y z lat lon Epoch 1995-01-01 -42.0 23.0 35.0 9.0 -80.0 -0.5 -2.0 -26.0 2015-03-23 -22.0 31.0 27.0 10.0 -70.0 0.5 -1.0 -36.0 2022-10-09 -34.0 11.0 49.0 8.0 -90.0 1.5 3.0 -16.0 >>> spacecraft = Spacecraft(spacecraft, "PSP", "HCI") """ super(Spacecraft, self).__init__(data) self.set_frame_name(frame, name) self.set_data(data) self._log_spacecraft()
@property def frame(self): r"""Spacecraft's frame of reference (e.g. GSE, HCI, etc.).""" return self._frame @property def name(self): r"""Spacecraft name (e.g. WIND, PSP).""" return self._name @property def position(self): """Position vector of the spacecraft. Returns ------- vector.Vector Position vector with x, y, z components. """ pos = self.data.xs("pos", axis=1, level="M").loc[:, ("x", "y", "z")] return vector.Vector(pos) @property def pos(self): """Shortcut to position property. Returns ------- vector.Vector Position vector with x, y, z components. """ # Ensures that `sc.pos` returns vector. return self.position @property def r(self): r"""Shortcut to :py:attr:`position`.""" return self.position @property def velocity(self): """Velocity vector of the spacecraft. Returns ------- vector.Vector Velocity vector with x, y, z components. Raises ------ KeyError If spacecraft velocity data is not available. """ try: v = self.data.xs("v", axis=1, level="M").loc[:, ("x", "y", "z")] return vector.Vector(v) except KeyError as e: # noqa: F841 raise KeyError("Spacecraft doesn't know it's velocity.") @property def v(self): r"""Shortcut to :py:attr:`velocity`.""" return self.velocity @property def carrington(self): r"""Carrington latitude and longitude.""" try: return self.data.xs("carr", axis=1, level="M").loc[:, ("lat", "lon")] except KeyError as e: # noqa: F841 raise KeyError("Spacecraft doesn't know its Carrington location.") @property def distance2sun(self): r"""Radial distance to Sun in meters.""" pos = self.position.data frame = self.frame if frame == "GSE": re = self.constants.misc.loc["Re [m]"] au = self.constants.misc.loc["1AU [m]"] sign_x = pd.Series( [-1.0, 1.0, 1.0], index=pd.Index(("x", "y", "z"), name="C") ) change_origin = pd.Series( [au, 0.0, 0.0], index=pd.Index(("x", "y", "z"), name="C") ) pos_SI = ( pos.multiply(sign_x, axis=1).multiply(re).add(change_origin, axis=1) ) elif frame == "HCI": rs = self.constants.misc.loc["Rs [m]"] pos_SI = pos.multiply(rs) else: raise NotImplementedError("Unrecognized reference frame `{}`".format(frame)) # distance2sun units should be [m], so this shouldn't matter. However, just as # beta is treated in this way, we similarly treat distance2sun. d2s = pos_SI.pow(2).sum(axis=1).pipe(np.sqrt) / self.units.distance2sun d2s.name = "distance2sun" return d2s def _log_spacecraft(self): self.logger.info( "Created %s spacecraft with %s reference frame", self.name, self.frame )
[docs] def set_frame_name(self, frame, name): """Set the coordinate frame and spacecraft name. Parameters ---------- frame : str Coordinate frame ('GSE' or 'HCI'). name : str Spacecraft name. Raises ------ NotImplementedError If frame is not 'GSE' or 'HCI'. """ frame = frame.upper() name = name.upper() if frame not in ("GSE", "HCI"): raise NotImplementedError("Unrecognized frame: {}".format(frame)) if name not in ("WIND", "PSP"): raise NotImplementedError("Unrecognized name: {}".format(name)) self._frame = frame self._name = name
[docs] def set_data(self, data): """Set the spacecraft data. Parameters ---------- data : pd.DataFrame Spacecraft position/velocity data. """ super(Spacecraft, self).set_data(data) p = data.xs("pos", axis=1, level="M") # assert isinstance(p, pd.DataFrame) p = p.loc[:, ["x", "y", "z"]] assert p.shape[1] == 3 target = {"pos": p} try: v = data.xs("v", axis=1, level="M") assert isinstance(v, pd.DataFrame) v = v.loc[:, ["x", "y", "z"]] assert v.shape[1] == 3 target["v"] = v except KeyError: pass try: c = data.xs("carr", axis=1, level="M") assert isinstance(c, pd.DataFrame) c = c.loc[:, ["lat", "lon"]] assert c.shape[1] == 2 target["carr"] = c except KeyError: pass target = pd.concat(target, axis=1, names=["M"], sort=False).sort_index(axis=1) assert isinstance(target.index, pd.DatetimeIndex) self._data = target