diff --git a/CHANGELOG.md b/CHANGELOG.md index 06d3a12..32ffd06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Moved population definitions to the base level and renamed it `population`. - Changed references to diameters in the broken power law sampler. - FOV propagation tests now return a flatten list of states. +- Renamed `data` to `cache` to more accurately represent its function. ### Fixed diff --git a/MANIFEST.in b/MANIFEST.in index 9465246..36126af 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1 @@ -include src/neospy/data/* -include src/neospy/_core* -exclude src/neospy/data/*.gz \ No newline at end of file +include src/neospy/_core* \ No newline at end of file diff --git a/docs/api/api.rst b/docs/api/api.rst index d86e4c3..20356e3 100644 --- a/docs/api/api.rst +++ b/docs/api/api.rst @@ -9,8 +9,10 @@ API .. toctree:: :maxdepth: 2 + cache conversion flux + fov interface observatory population diff --git a/docs/api/cache.rst b/docs/api/cache.rst new file mode 100644 index 0000000..162031c --- /dev/null +++ b/docs/api/cache.rst @@ -0,0 +1,8 @@ +cache +===== + +Tools to interact with NEOSpy's Cache folder. + +.. automodule:: neospy.cache + :members: + :inherited-members: \ No newline at end of file diff --git a/docs/api/fov.rst b/docs/api/fov.rst new file mode 100644 index 0000000..1fcc2ee --- /dev/null +++ b/docs/api/fov.rst @@ -0,0 +1,8 @@ +fov +=== + +Field of View + +.. automodule:: neospy.fov + :members: + :inherited-members: \ No newline at end of file diff --git a/docs/api/spice.rst b/docs/api/spice.rst index 4af3c2c..b4e9a33 100644 --- a/docs/api/spice.rst +++ b/docs/api/spice.rst @@ -1,6 +1,28 @@ spice ===== +This is a thread-safe, read only re-implementation of a SPICE kernel interpreter. +Outputs of this exactly match the common cSPICE interpreter, but can be easily +used among an arbitrary number of cores. SPICE kernel files are loaded directly +into RAM. + +.. note:: + + This does not use cSPICE, or any original SPICE library code. + + cSPICE is difficult to use in a thread safe manner which is limiting when + performing orbit calculations on millions of objects. + +Data files which are automatically included: + +DE440 - A SPICE file containing the planets within a few hundred years. + +BSP files are also automatically included for the 5 largest asteroids, which are +used for numerical integrations when the correct flags are set. + +PCK Files which enable coordinate transformations between Earths surface and the +common inertial frames. + .. automodule:: neospy.spice :members: :inherited-members: diff --git a/docs/api/vector.rst b/docs/api/vector.rst index 7dfc879..d09f93d 100644 --- a/docs/api/vector.rst +++ b/docs/api/vector.rst @@ -1,5 +1,9 @@ -core -==== +vectors/states/frames +===================== + +Units used throughout NEOSpy are distance in au, and time in Days TDB scaled. + +Coordinate frames match the coordinate frames used by cSPICE. .. automodule:: neospy.vector :members: Vector diff --git a/docs/conf.py b/docs/conf.py index 7491688..d03798c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,6 +22,10 @@ "sphinx_gallery.gen_gallery", "matplotlib.sphinxext.plot_directive", ] + +suppress_warnings = ["config.cache"] + + autodoc_typehints = "description" autodoc_inherit_docstrings = True autodoc_warningiserror = True diff --git a/src/neospy/__init__.py b/src/neospy/__init__.py index 0bb3b0a..db10f9f 100644 --- a/src/neospy/__init__.py +++ b/src/neospy/__init__.py @@ -1,10 +1,16 @@ -from .vector import Vector, Frames, State, CometElements +from .vector import ( + Vector, + Frames, + State, + CometElements, + SimultaneousStates, +) from . import ( constants, covariance, wise, neos, - data, + cache, population, flux, mpc, @@ -30,18 +36,14 @@ ) from .horizons import HorizonsProperties - -# pylint: disable-next=import-error -from ._core import SimultaneousStates # type: ignore - import logging __all__ = [ + "cache", "constants", "CometElements", "covariance", - "data", "irsa", "Frames", "moid", diff --git a/src/neospy/data.py b/src/neospy/cache.py similarity index 100% rename from src/neospy/data.py rename to src/neospy/cache.py diff --git a/src/neospy/horizons.py b/src/neospy/horizons.py index 27f7d0b..34c05b8 100644 --- a/src/neospy/horizons.py +++ b/src/neospy/horizons.py @@ -160,7 +160,7 @@ def _fetch_json( have fragments, as these objects are difficult to query since there are several names which have matches on substrings. """ - from .data import cache_path + from .cache import cache_path from .mpc import unpack_designation, pack_designation import json diff --git a/src/neospy/mpc.py b/src/neospy/mpc.py index a44c495..9acb41e 100644 --- a/src/neospy/mpc.py +++ b/src/neospy/mpc.py @@ -10,7 +10,7 @@ from . import conversion, constants from .time import float_day_to_d_h_m_s, Time from .vector import Vector, Frames, CometElements -from .data import cached_gzip_json_download +from .cache import cached_gzip_json_download # pylint: disable-next=no-name-in-module from . import _core # type: ignore diff --git a/src/neospy/rust/fovs/collection.rs b/src/neospy/rust/fovs/collection.rs index efb5db5..003b655 100644 --- a/src/neospy/rust/fovs/collection.rs +++ b/src/neospy/rust/fovs/collection.rs @@ -43,6 +43,7 @@ impl FOVList { FOVList(list) } + /// Sort the list of FOVs by their JD. pub fn sort(&mut self) { self.0.sort_by(|a, b| a.jd().total_cmp(&b.jd())) } diff --git a/src/neospy/rust/fovs/definitions.rs b/src/neospy/rust/fovs/definitions.rs index c91c025..4a7e359 100644 --- a/src/neospy/rust/fovs/definitions.rs +++ b/src/neospy/rust/fovs/definitions.rs @@ -141,30 +141,35 @@ impl PyWiseCmos { )) } + /// Position of the observer in this FOV. #[getter] pub fn observer(&self) -> PyState { self.0.observer().clone().into() } + /// Direction that the observer is looking. #[getter] pub fn pointing(&self) -> Vector { let pointing = self.0.patch.pointing().into_inner().into(); Vector::new(pointing, self.0.patch.frame.into()) } + /// WISE Frame number. #[getter] pub fn frame_num(&self) -> usize { self.0.frame_num } + /// WISE Scan ID. #[getter] pub fn scan_id(&self) -> String { self.0.scan_id.to_string() } + /// Rotation angle of the FOV in degrees. #[getter] pub fn rotation(&self) -> f64 { - self.0.rotation + self.0.rotation.to_degrees() } fn __repr__(&self) -> String { @@ -200,17 +205,27 @@ impl PyGenericRectangle { } /// Construct a new Rectangle FOV from the corners. + /// The corners must be provided in clockwise order. + /// + /// Parameters + /// ---------- + /// corners : + /// 4 Vectors which represent the corners of the FOV, these must be provided in clockwise order. + /// observer : + /// Position of the observer as a State. #[staticmethod] pub fn from_corners(corners: [VectorLike; 4], observer: PyState) -> Self { let corners: [Vector3; 4] = corners.map(|x| x.into_vec(observer.frame())); PyGenericRectangle(fov::GenericRectangle::from_corners(corners, observer.0)) } + /// The observer State. #[getter] pub fn observer(&self) -> PyState { self.0.observer().clone().into() } + /// Direction that the observer is looking. #[getter] pub fn pointing(&self) -> Vector { Vector::new( @@ -219,11 +234,13 @@ impl PyGenericRectangle { ) } + /// The longitudinal width of the FOV. #[getter] pub fn lon_width(&self) -> f64 { self.0.lon_width().to_degrees() } + /// The Latitudinal width of the FOV. #[getter] pub fn lat_width(&self) -> f64 { self.0.lat_width().to_degrees() @@ -243,6 +260,32 @@ impl PyGenericRectangle { #[pymethods] #[allow(clippy::too_many_arguments)] impl PyNeosCmos { + /// Construct a new NEOS FOV. + /// + /// Parameters + /// ---------- + /// pointing : + /// Vector defining the center of the FOV. + /// rotation : + /// Rotation of the FOV in degrees. + /// observer : + /// State of the observer. + /// side_id : + /// Side ID indicating where we are in the survey. + /// stack_id : + /// Stack ID indicating where we are in the survey. + /// quad_id : + /// Quad ID indicating where we are in the survey. + /// loop_id : + /// Loop ID indicating where we are in the survey. + /// subloop_id : + /// Subloop ID indicating where we are in the survey. + /// exposure_id : + /// Exposure number indicating where we are in the survey. + /// cmos_id : + /// Which chip of the target band this represents. + /// band : + /// Band, can be either 1 or 2 to represent NC1/NC2. #[new] pub fn new( pointing: VectorLike, @@ -274,11 +317,13 @@ impl PyNeosCmos { )) } + /// The observer State. #[getter] pub fn observer(&self) -> PyState { self.0.observer().clone().into() } + /// Direction that the observer is looking. #[getter] pub fn pointing(&self) -> Vector { Vector::new( @@ -287,39 +332,46 @@ impl PyNeosCmos { ) } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn side_id(&self) -> u16 { self.0.side_id } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn stack_id(&self) -> u8 { self.0.stack_id } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn quad_id(&self) -> u8 { self.0.quad_id } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn loop_id(&self) -> u8 { self.0.loop_id } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn subloop_id(&self) -> u8 { self.0.subloop_id } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn exposure_id(&self) -> u8 { self.0.exposure_id } + /// Rotation angle of the FOV in degrees. #[getter] pub fn rotation(&self) -> f64 { - self.0.rotation + self.0.rotation.to_degrees() } fn __repr__(&self) -> String { @@ -341,6 +393,31 @@ impl PyNeosCmos { #[pymethods] #[allow(clippy::too_many_arguments)] impl PyZtfCcdQuad { + /// Construct a new ZTF CCD FOV from the corners. + /// The corners must be provided in clockwise order. + /// + /// Parameters + /// ---------- + /// corners : + /// 4 Vectors which represent the corners of the FOV, these must be provided in clockwise order. + /// observer : + /// Position of the observer as a State. + /// field : + /// Field number of the survey. + /// filefracday : + /// Which fraction of a day was this FOV captured. + /// ccdid : + /// CCD ID describes which of the 16 CCDs this represents. + /// filtercode : + /// Which filter was used for this exposure. + /// imgtypecode : + /// Type code describing the data product of this field. + /// qid : + /// Which quadrant of the CCD does this FOV represent. + /// maglimit : + /// Effective magnitude limit of this exposure. + /// fid : + /// The FID of this exposure. #[new] pub fn new( corners: [VectorLike; 4], @@ -379,17 +456,13 @@ impl PyZtfCcdQuad { self.0.observer().clone().into() } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn field(&self) -> u32 { self.0.field } - #[getter] - pub fn ra(&self) -> f64 { - let pointing = self.pointing(); - pointing.as_equatorial().ra().unwrap() - } - + /// Direction that the observer is looking. #[getter] pub fn pointing(&self) -> Vector { Vector::new( @@ -398,42 +471,43 @@ impl PyZtfCcdQuad { ) } - #[getter] - pub fn dec(&self) -> f64 { - let pointing = self.pointing(); - pointing.as_equatorial().dec().unwrap() - } - + /// Metadata about where this FOV is in the Survey. #[getter] pub fn filefracday(&self) -> u64 { self.0.filefracday } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn ccdid(&self) -> u8 { self.0.ccdid } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn filtercode(&self) -> String { self.0.filtercode.to_string() } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn imgtypecode(&self) -> String { self.0.imgtypecode.to_string() } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn qid(&self) -> u8 { self.0.qid } + /// Magnitude limit of this exposure. #[getter] pub fn maglimit(&self) -> f64 { self.0.maglimit } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn fid(&self) -> usize { self.0.fid @@ -441,9 +515,7 @@ impl PyZtfCcdQuad { fn __repr__(&self) -> String { format!( - "ZTFFOV(ra={}, dec={}, observer={}, filefracday={}, ccdid={}, filtercode={}, imgtypecode={}, qid={}, maglimit={}, fid={})", - self.ra(), - self.dec(), + "ZTFFOV(corners=, observer={}, filefracday={}, ccdid={}, filtercode={}, imgtypecode={}, qid={}, maglimit={}, fid={})", self.observer().__repr__(), self.filefracday(), self.ccdid(), @@ -459,36 +531,49 @@ impl PyZtfCcdQuad { #[pymethods] #[allow(clippy::too_many_arguments)] impl PyZtfField { + /// Representation of an entire ZTF Field, made up of up to 64 ZTF CCD FOVs. + /// + /// Parameters + /// ---------- + /// ztf_ccd_fields : + /// List containing all of the CCD FOVs. + /// These must have matching metadata. #[new] pub fn new(ztf_ccd_fields: Vec) -> Self { PyZtfField(fov::ZtfField::new(ztf_ccd_fields.into_iter().map(|x| x.0).collect()).unwrap()) } + /// State of the observer for this FOV. #[getter] pub fn observer(&self) -> PyState { self.0.observer().clone().into() } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn field(&self) -> u32 { self.0.field } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn filtercode(&self) -> String { self.0.filtercode.to_string() } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn imgtypecode(&self) -> String { self.0.imgtypecode.to_string() } + /// Metadata about where this FOV is in the Survey. #[getter] pub fn fid(&self) -> usize { self.0.fid } + /// Retrieve a specific CCD FOV from the contained list of FOVs. pub fn get_ccd(&self, idx: usize) -> PyZtfCcdQuad { PyZtfCcdQuad(match self.0.get_fov(idx) { fov::FOV::ZtfCcdQuad(fov) => fov, diff --git a/src/neospy/rust/simult_states.rs b/src/neospy/rust/simult_states.rs index 34a1fda..ab92037 100644 --- a/src/neospy/rust/simult_states.rs +++ b/src/neospy/rust/simult_states.rs @@ -37,6 +37,12 @@ impl SimulStateLike { } } +/// Representation of a collection of [`State`] at a single point in time. +/// +/// The main value in this is that also includes an optional Field of View. +/// If the FOV is provided, it is implied that the states which are present +/// in this file were objects seen by the FOV. +/// #[pyclass(module = "neospy", frozen, sequence, name = "SimultaneousStates")] #[derive(Clone, Debug)] pub struct PySimultaneousStates(pub Box); @@ -49,6 +55,16 @@ impl From for PySimultaneousStates { #[pymethods] impl PySimultaneousStates { + /// Create a new collection of States at a specific time. + /// + /// + /// Parameters + /// ---------- + /// states : + /// List of States to include. + /// fov : + /// An optional FOV, if this is provided it is expected that the states provided + /// are what have been seen by this FOV. This is not checked. #[new] pub fn new(states: Vec, fov: Option) -> PyResult { let states: Vec<_> = states.into_iter().map(|x| x.0).collect(); @@ -57,31 +73,37 @@ impl PySimultaneousStates { .map(|x| PySimultaneousStates(Box::new(x)))?) } + /// The FOV if it exists. #[getter] pub fn fov(&self) -> Option { self.0.fov.clone().map(|x| x.into()) } + /// States contained within. #[getter] pub fn states(&self) -> Vec { self.0.states.iter().map(|x| x.clone().into()).collect() } + /// The time of the simultaneous states. #[getter] pub fn jd(&self) -> f64 { self.0.jd } + /// The reference center NAIF ID for this state. #[getter] pub fn center_id(&self) -> isize { self.0.center_id } + /// Coordinate Frame. #[getter] pub fn frame(&self) -> PyFrames { self.0.frame.into() } + /// Load a single SimultaneousStates from a file. #[staticmethod] pub fn load(filename: String) -> PyResult { Ok(PySimultaneousStates(Box::new( @@ -89,6 +111,7 @@ impl PySimultaneousStates { ))) } + /// Save a single SimultaneousStates to a file. pub fn save(&self, filename: String) -> PyResult<()> { self.0.save(filename)?; Ok(()) @@ -118,6 +141,8 @@ impl PySimultaneousStates { } /// Save a list to a binary file. + /// + /// Note that this saves a list of SimultaneousStates, meaning it is a list of a list of States. #[staticmethod] #[pyo3(name = "save_list")] pub fn py_save_list(vec: Vec, filename: String) -> PyResult<()> { @@ -128,6 +153,8 @@ impl PySimultaneousStates { } /// Load a list from a binary file. + /// + /// Note that this loads a list of SimultaneousStates, meaning it is a list of a list of States. #[staticmethod] #[pyo3(name = "load_list")] pub fn py_load_list(filename: String) -> PyResult> { diff --git a/src/neospy/spice.py b/src/neospy/spice.py index c0fa14a..19376ec 100644 --- a/src/neospy/spice.py +++ b/src/neospy/spice.py @@ -9,7 +9,7 @@ from .time import Time from . import _core # pylint: disable=no-name-in-module from .constants import AU_KM -from .data import cached_file_download, cache_path +from .cache import cached_file_download, cache_path from .vector import Frames, State __all__ = ["SpiceKernels"] diff --git a/src/neospy/vector.py b/src/neospy/vector.py index f74e647..34e3747 100644 --- a/src/neospy/vector.py +++ b/src/neospy/vector.py @@ -1,15 +1,21 @@ """ -Representation of States and vectors. +Representation of States, Vectors, and coordinate Frames. """ from __future__ import annotations from . import conversion # pylint: disable=import-error -from ._core import Vector, Frames, State, CometElements # type: ignore +from ._core import ( # type: ignore + Vector, + Frames, + State, + CometElements, + SimultaneousStates, +) -__all__ = ["Frames", "Vector", "State", "CometElements"] +__all__ = ["Frames", "Vector", "State", "CometElements", "SimultaneousStates"] Vector.dec_dms = property( fget=lambda self: conversion.dec_degrees_to_dms(self.dec), diff --git a/src/neospy/wise.py b/src/neospy/wise.py index feb57a0..801285e 100644 --- a/src/neospy/wise.py +++ b/src/neospy/wise.py @@ -18,7 +18,7 @@ from astropy.coordinates import SkyCoord # type: ignore -from .data import cache_path +from .cache import cache_path from .time import Time from .spice import SpiceKernels from .vector import Vector diff --git a/src/neospy/ztf.py b/src/neospy/ztf.py index 58255b0..4668011 100644 --- a/src/neospy/ztf.py +++ b/src/neospy/ztf.py @@ -1,9 +1,13 @@ +""" +ZTF Related Functions and Data. +""" + from functools import lru_cache import os -import numpy as np from collections import defaultdict +import numpy as np -from .data import cached_file_download, cache_path +from .cache import cached_file_download, cache_path from .fov import ZtfCcdQuad, ZtfField, FOVList from .time import Time from .irsa import query_irsa_tap @@ -106,8 +110,8 @@ def fetch_ZTF_fovs(year: int): for jd, row in zip(jds, irsa_query.itertuples()): corners = [] for i in range(4): - ra = row.__getattribute__(f"ra{i+1}") - dec = row.__getattribute__(f"dec{i+1}") + ra = getattr(row, f"ra{i + 1}") + dec = getattr(row, f"dec{i + 1}") corners.append(Vector.from_ra_dec(ra, dec)) observer = SpiceKernels.earth_pos_to_ecliptic(jd, *obs_info[:-1])