Skip to content

Commit

Permalink
add epoch propagation
Browse files Browse the repository at this point in the history
  • Loading branch information
somilia committed Nov 23, 2023
1 parent eefbf35 commit 46b4362
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 65 deletions.
25 changes: 11 additions & 14 deletions docs/mivot/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,16 @@ The API allows you to obtain a model view on the last read data row, this usage

.. doctest-remote-data::
>>> import pyvo
>>> import os
>>> from astropy.utils.data import get_pkg_data_filename
>>> import astropy.units as u
>>> from pyvo.mivot.viewer.model_viewer import ModelViewer
>>> from pyvo.mivot.version_checker import check_astropy_version
>>> from pyvo.utils.prototype import activate_features
>>> activate_features('MIVOT')
>>> votable = get_pkg_data_filename("data/simple-annotation-votable.xml", package="pyvo.mivot.tests")
>>> if check_astropy_version(): # doctest: +SKIP
... m_viewer = ModelViewer(votable)
... row_view = m_viewer.get_next_row_view()
... print(row_view.longitude.value)
... print(row_view.Coordinate_coosys.PhysicalCoordSys_frame.spaceRefFrame.value)
>>> m_viewer = ModelViewer(votable) # doctest: +SKIP
>>> row_view = m_viewer.get_next_row_view() # doctest: +SKIP
>>> print(row_view.longitude.value) # doctest: +SKIP
>>> print(row_view.Coordinate_coosys.PhysicalCoordSys_frame.spaceRefFrame.value) # doctest: +SKIP
10.0
ICRS

Expand All @@ -52,13 +50,12 @@ the dmroles of the MIVOT elements. There is no checking against the model struct
Example for epoch propagation
-----------------------------
.. doctest-remote-data::
>>> if check_astropy_version():
... with ModelViewer(votable) as m_viewer: # doctest: +SKIP
... row_view = m_viewer.get_next_row_view()
... past_ra, past_dec = row_view.apply_space_motion(dt=-42 * u.year)
... future_ra, future_dec = row_view.apply_space_motion(dt=2 * u.year)
... print("past_ra, past_dec :", row_view.apply_space_motion(dt=-42 * u.year))
... print("future_ra, future_dec :", row_view.apply_space_motion(dt=2 * u.year))
>>> with ModelViewer(votable) as m_viewer: # doctest: +SKIP
... row_view = m_viewer.get_next_row_view()
... past_ra, past_dec = row_view.epoch_propagation.apply_space_motion(dt=-42 * u.year)
... future_ra, future_dec = row_view.epoch_propagation.apply_space_motion(dt=2 * u.year)
... print("past_ra, past_dec :", row_view.epoch_propagation.apply_space_motion(dt=-42 * u.year))
... print("future_ra, future_dec :", row_view.epoch_propagation.apply_space_motion(dt=2 * u.year))
past_ra, past_dec : (<Longitude 9.9998763 deg>, <Latitude 10.00024364 deg>)
future_ra, future_dec : (<Longitude 10.00000563 deg>, <Latitude 9.99998891 deg>)

Expand Down
89 changes: 89 additions & 0 deletions pyvo/mivot/features/epoch_propagation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
Implementation of the EpochPropagation in the MIVOT class with
astropy.coordinates.sky_coordinate.SkyCoord and
astropy.coordinates.sky_coordinate.SkyCoord.apply_space_motion.
"""
from astropy.coordinates import SkyCoord
import astropy.units as u
from pyvo.utils.prototype import prototype_feature


@prototype_feature('MIVOT')
class EpochPropagation:
"""
This class allows computing the position of a SkyCoord object at a new time dt.
It builds a SkyCoord object from the data row of the MivotClass, and then applies the space motion.
"""
fields = ["longitude", "latitude", "pm_longitude", "pm_latitude"]

def __init__(self, name):
self.REFERENCE = {}
self.name = name

@property
def ref_long(self):
return self.REFERENCE.get("longitude")

@ref_long.setter
def ref_long(self, value):
self.REFERENCE["longitude"] = value

@property
def ref_lat(self):
return self.REFERENCE.get("latitude")

@ref_lat.setter
def ref_lat(self, value):
self.REFERENCE["latitude"] = value

@property
def ref_pm_long(self):
return self.REFERENCE.get("pm_longitude")

@ref_pm_long.setter
def ref_pm_long(self, value):
self.REFERENCE["pm_longitude"] = value

@property
def ref_pm_lat(self):
return self.REFERENCE.get("pm_latitude")

@ref_pm_lat.setter
def ref_pm_lat(self, value):
self.REFERENCE["pm_latitude"] = value

def SkyCoordinate(self):
"""
Returns a SkyCoord object from the REFERENCE of the XML object.
"""
if self.REFERENCE["frame"] == ('icrs' or 'fk5' or 'fk4'):
return SkyCoord(distance=(self.REFERENCE["parallax"] / 4) * u.pc,
radial_velocity=self.REFERENCE["radial_velocity"] * u.km / u.s,
ra=self.REFERENCE["longitude"] * u.degree,
dec=self.REFERENCE["latitude"] * u.degree,
pm_ra_cosdec=self.REFERENCE["pm_longitude"] * u.mas / u.yr,
pm_dec=self.REFERENCE["pm_latitude"] * u.mas / u.yr,
frame=self.REFERENCE["frame"],
obstime=self.REFERENCE["epoch"])

elif self.REFERENCE["frame"] == 'galatic':
return SkyCoord(distance=self.REFERENCE["parallax"] * u.pc,
l=self.REFERENCE["longitude"] * u.degree,
b=self.REFERENCE["latitude"] * u.degree,
pm_l_cosb=self.REFERENCE["pm_longitude"] * u.mas / u.yr,
pm_b=self.REFERENCE["pm_latitude"] * u.mas / u.yr,
frame=self.REFERENCE["frame"],
obstime=self.REFERENCE["epoch"])

def apply_space_motion(self, dt):
"""
Returns ra and dec of a SkyCoord object by computing the position to a new time dt.
Parameters
----------
dt : float
Time in years.
"""
retour = self.SkyCoordinate().apply_space_motion(dt=dt)
return retour.ra, retour.dec
58 changes: 58 additions & 0 deletions pyvo/mivot/tests/test_epoch_propagation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""
Test for mivot.seekers.annotation_seeker.py
"""
import os

import pytest
from astropy.coordinates import SkyCoord
import astropy.units as u
from astropy.time import Time

from pyvo.mivot.version_checker import check_astropy_version
from pyvo.mivot.viewer.model_viewer import ModelViewer
from pyvo.utils import activate_features

activate_features('MIVOT')


def test_epoch_propagation(m_viewer):
if check_astropy_version() is False:
pytest.skip("MIVOT test skipped because of the astropy version.")

row_view = m_viewer.get_next_row_view()
epoch_propagation = row_view.EpochPropagation
sky_coord_to_compare = (SkyCoord(distance=(row_view.parallax.value / 4) * u.pc,
radial_velocity=row_view.radialVelocity.value * u.km / u.s,
ra=row_view.longitude.value * u.degree,
dec=row_view.latitude.value * u.degree,
pm_ra_cosdec=row_view.latitude.value * u.mas / u.yr,
pm_dec=row_view.pmLatitude.value * u.mas / u.yr,
frame=row_view.Coordinate_coosys
.PhysicalCoordSys_frame.spaceRefFrame.value.lower(),
obstime=Time(row_view.epoch.value, format="decimalyear")))

assert sky_coord_to_compare == epoch_propagation.SkyCoordinate()
assert ((sky_coord_to_compare.apply_space_motion(dt=-42 * u.year).ra,
sky_coord_to_compare.apply_space_motion(dt=-42 * u.year).dec)
== epoch_propagation.apply_space_motion(dt=-42 * u.year))
assert epoch_propagation.ref_long == 10.0
assert epoch_propagation.ref_lat == 10.0
assert epoch_propagation.ref_pm_long == 10.0
assert epoch_propagation.ref_pm_lat == -20.0

@pytest.fixture
def m_viewer(data_path):
if check_astropy_version() is False:
pytest.skip("MIVOT test skipped because of the astropy version.")
votable = os.path.join(data_path, "data/simple-annotation-votable.xml")
return ModelViewer(votable_path=votable)


@pytest.fixture
def data_path():
return os.path.dirname(os.path.realpath(__file__))


if __name__ == '__main__':
pytest.main()
1 change: 0 additions & 1 deletion pyvo/mivot/version_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
def check_astropy_version():
"""
Check if the installed version of astropy is compatible with MIVOT.
Check if defusedxml is installed.
"""
if not astropy_version.version:
return False
Expand Down
77 changes: 27 additions & 50 deletions pyvo/mivot/viewer/mivot_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
"""
MivotClass keep as an attribute dictionary __dict__ all XML objects.
"""
from astropy.coordinates import SkyCoord
import astropy.units as u
from astropy.time import Time

from pyvo.mivot.features.epoch_propagation import EpochPropagation
from pyvo.utils.prototype import prototype_feature


Expand All @@ -19,6 +18,7 @@ class MivotClass:
"key" : "value" means key is an element of ATTRIBUTE
"key" : [] means key is the dmtype of a COLLECTION
"""
EpochPropagation = EpochPropagation("EpochPropagation")
REFERENCE = {}

def __init__(self, **kwargs):
Expand All @@ -42,63 +42,40 @@ def __init__(self, **kwargs):
else:
if isinstance(value, dict) and self._is_leaf(**value):
self.__dict__[self._remove_model_name(key)] = MivotClass(**value)
key_low = key.lower()
if self.dmtype == "EpochPosition":
if ("longitude" or "ra") in key_low:
if "pm" not in key_low and value["unit"] == "deg":
MivotClass.REFERENCE["longitude"] = value['value']
elif "pm" in key_low and value["unit"] == "mas/year":
MivotClass.REFERENCE["pm_longitude"] = value['value']
if ("latitude" or "dec") in key_low:
if "pm" not in key_low and value["unit"] == "deg":
MivotClass.REFERENCE["latitude"] = value['value']
elif "pm" in key_low and value["unit"] == "mas/year":
MivotClass.REFERENCE["pm_latitude"] = value['value']
if ("radial" or "velocity") in key_low and value["unit"] == "km/s":
MivotClass.REFERENCE["radial_velocity"] = value["value"]
if "parallax" in key_low and value["unit"] == ("mas" or "pc"):
MivotClass.REFERENCE["parallax"] = value["value"]
if "epoch" in key_low and value["unit"] == "year":
MivotClass.REFERENCE["epoch"] = Time(value["value"], format="decimalyear")
if "frame" in key_low and "string" in value["dmtype"]:
MivotClass.REFERENCE["frame"] = value["value"].lower()
self._fill_epoch_propagation(key.lower(), value)
if "frame" in key.lower() and "string" in value["dmtype"]:
self.EpochPropagation.REFERENCE["frame"] = value["value"].lower()
else:
self.__dict__[self._remove_model_name(key)] = self._remove_model_name(value)

def SkyCoordinate(self):
def _fill_epoch_propagation(self, key_low, value):
"""
Returns a SkyCoord object from the REFERENCE of the XML object.
"""
if MivotClass.REFERENCE["frame"] == ('icrs' or 'fk5' or 'fk4'):
return SkyCoord(distance=(MivotClass.REFERENCE["parallax"] / 4) * u.pc,
radial_velocity=MivotClass.REFERENCE["radial_velocity"] * u.km / u.s,
ra=MivotClass.REFERENCE["longitude"] * u.degree,
dec=MivotClass.REFERENCE["latitude"] * u.degree,
pm_ra_cosdec=MivotClass.REFERENCE["pm_longitude"] * u.mas / u.yr,
pm_dec=MivotClass.REFERENCE["pm_latitude"] * u.mas / u.yr,
frame=MivotClass.REFERENCE["frame"],
obstime=MivotClass.REFERENCE["epoch"])

elif MivotClass.REFERENCE["frame"] == 'galatic':
return SkyCoord(l=MivotClass.REFERENCE["longitude"] * u.degree,
b=MivotClass.REFERENCE["latitude"] * u.degree,
distance=MivotClass.REFERENCE["parallax"] * u.pc,
pm_l_cosb=MivotClass.REFERENCE["pm_longitude"] * u.mas / u.yr,
pm_b=MivotClass.REFERENCE["pm_latitude"] * u.mas / u.yr,
frame=MivotClass.REFERENCE["frame"],
obstime=MivotClass.REFERENCE["epoch"])

def apply_space_motion(self, dt):
"""
Returns ra and dec of a SkyCoord object by computing the position to a new time dt.
Fill the REFERENCE dictionary of the EpochPropagation object.
Parameters
----------
dt : float
Time in years.
key_low : str
The key of the dictionary in lowercase.
value : dict
The value of the dictionary.
"""
retour = self.SkyCoordinate().apply_space_motion(dt=dt)
return retour.ra, retour.dec
if ("longitude" or "ra") in key_low:
if "pm" not in key_low and value["unit"] == "deg":
self.EpochPropagation.REFERENCE["longitude"] = value['value']
elif "pm" in key_low and value["unit"] == "mas/year":
self.EpochPropagation.REFERENCE["pm_longitude"] = value['value']
if ("latitude" or "dec") in key_low:
if "pm" not in key_low and value["unit"] == "deg":
self.EpochPropagation.REFERENCE["latitude"] = value['value']
elif "pm" in key_low and value["unit"] == "mas/year":
self.EpochPropagation.REFERENCE["pm_latitude"] = value['value']
if ("radial" or "velocity") in key_low and value["unit"] == "km/s":
self.EpochPropagation.REFERENCE["radial_velocity"] = value["value"]
if "parallax" in key_low and value["unit"] == ("mas" or "pc"):
self.EpochPropagation.REFERENCE["parallax"] = value["value"]
if "epoch" in key_low and value["unit"] == "year":
self.EpochPropagation.REFERENCE["epoch"] = Time(value["value"], format="decimalyear")

def _remove_model_name(self, value, role_instance=False):
"""
Expand Down

0 comments on commit 46b4362

Please sign in to comment.