From 131b9e5c81e78e783f08dbff34930b691f1d3e4e Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Wed, 30 Oct 2024 22:45:49 -0600 Subject: [PATCH 1/6] Initial work on type hints --- anise-py/anise.pyi | 0 anise-py/generate_stubs.py | 533 ++++++++++++++++++ anise/src/almanac/metaload/metaalmanac.rs | 13 +- anise/src/almanac/metaload/metafile.rs | 24 +- anise/src/astro/aberration.rs | 42 +- anise/src/frames/frame.rs | 55 +- .../src/structure/planetocentric/ellipsoid.rs | 50 +- 7 files changed, 702 insertions(+), 15 deletions(-) create mode 100644 anise-py/anise.pyi create mode 100644 anise-py/generate_stubs.py diff --git a/anise-py/anise.pyi b/anise-py/anise.pyi new file mode 100644 index 00000000..e69de29b diff --git a/anise-py/generate_stubs.py b/anise-py/generate_stubs.py new file mode 100644 index 00000000..8da22cd0 --- /dev/null +++ b/anise-py/generate_stubs.py @@ -0,0 +1,533 @@ +import argparse +import ast +import importlib +import inspect +import logging +import re +import subprocess +from functools import reduce +from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Union + + +def path_to_type(*elements: str) -> ast.AST: + base: ast.AST = ast.Name(id=elements[0], ctx=ast.Load()) + for e in elements[1:]: + base = ast.Attribute(value=base, attr=e, ctx=ast.Load()) + return base + + +OBJECT_MEMBERS = dict(inspect.getmembers(object)) +BUILTINS: Dict[str, Union[None, Tuple[List[ast.AST], ast.AST]]] = { + "__annotations__": None, + "__bool__": ([], path_to_type("bool")), + "__bytes__": ([], path_to_type("bytes")), + "__class__": None, + "__contains__": ([path_to_type("typing", "Any")], path_to_type("bool")), + "__del__": None, + "__delattr__": ([path_to_type("str")], path_to_type("None")), + "__delitem__": ([path_to_type("typing", "Any")], path_to_type("typing", "Any")), + "__dict__": None, + "__dir__": None, + "__doc__": None, + "__eq__": ([path_to_type("typing", "Any")], path_to_type("bool")), + "__format__": ([path_to_type("str")], path_to_type("str")), + "__ge__": ([path_to_type("typing", "Any")], path_to_type("bool")), + "__getattribute__": ([path_to_type("str")], path_to_type("typing", "Any")), + "__getitem__": ([path_to_type("typing", "Any")], path_to_type("typing", "Any")), + "__gt__": ([path_to_type("typing", "Any")], path_to_type("bool")), + "__hash__": ([], path_to_type("int")), + "__init__": ([], path_to_type("None")), + "__init_subclass__": None, + "__iter__": ([], path_to_type("typing", "Any")), + "__le__": ([path_to_type("typing", "Any")], path_to_type("bool")), + "__len__": ([], path_to_type("int")), + "__lt__": ([path_to_type("typing", "Any")], path_to_type("bool")), + "__module__": None, + "__ne__": ([path_to_type("typing", "Any")], path_to_type("bool")), + "__new__": None, + "__next__": ([], path_to_type("typing", "Any")), + "__int__": ([], path_to_type("None")), + "__reduce__": None, + "__reduce_ex__": None, + "__repr__": ([], path_to_type("str")), + "__setattr__": ( + [path_to_type("str"), path_to_type("typing", "Any")], + path_to_type("None"), + ), + "__setitem__": ( + [path_to_type("typing", "Any"), path_to_type("typing", "Any")], + path_to_type("typing", "Any"), + ), + "__sizeof__": None, + "__str__": ([], path_to_type("str")), + "__subclasshook__": None, +} + + +def module_stubs(module: Any) -> ast.Module: + types_to_import = {"typing"} + classes = [] + functions = [] + for member_name, member_value in inspect.getmembers(module): + element_path = [module.__name__, member_name] + if member_name.startswith("__"): + pass + elif member_name.startswith("DoraStatus"): + pass + elif inspect.isclass(member_value): + classes.append( + class_stubs(member_name, member_value, element_path, types_to_import) + ) + elif inspect.isbuiltin(member_value): + functions.append( + function_stub( + member_name, + member_value, + element_path, + types_to_import, + in_class=False, + ) + ) + else: + logging.warning(f"Unsupported root construction {member_name}") + return ast.Module( + body=[ast.Import(names=[ast.alias(name=t)]) for t in sorted(types_to_import)] + + classes + + functions, + type_ignores=[], + ) + + +def class_stubs( + cls_name: str, cls_def: Any, element_path: List[str], types_to_import: Set[str] +) -> ast.ClassDef: + attributes: List[ast.AST] = [] + methods: List[ast.AST] = [] + magic_methods: List[ast.AST] = [] + constants: List[ast.AST] = [] + for member_name, member_value in inspect.getmembers(cls_def): + current_element_path = [*element_path, member_name] + if member_name == "__init__" and "Error" not in cls_name: + try: + inspect.signature(cls_def) # we check it actually exists + methods = [ + function_stub( + member_name, + cls_def, + current_element_path, + types_to_import, + in_class=True, + ), + *methods, + ] + except ValueError as e: + if "no signature found" not in str(e): + raise ValueError( + f"Error while parsing signature of {cls_name}.__init_" + ) from e + elif ( + member_value == OBJECT_MEMBERS.get(member_name) + or BUILTINS.get(member_name, ()) is None + ): + pass + elif inspect.isdatadescriptor(member_value): + attributes.extend( + data_descriptor_stub( + member_name, member_value, current_element_path, types_to_import + ) + ) + elif inspect.isroutine(member_value): + (magic_methods if member_name.startswith("__") else methods).append( + function_stub( + member_name, + member_value, + current_element_path, + types_to_import, + in_class=True, + ) + ) + elif member_name == "__match_args__": + constants.append( + ast.AnnAssign( + target=ast.Name(id=member_name, ctx=ast.Store()), + annotation=ast.Subscript( + value=path_to_type("tuple"), + slice=ast.Tuple( + elts=[path_to_type("str"), ast.Ellipsis()], ctx=ast.Load() + ), + ctx=ast.Load(), + ), + value=ast.Constant(member_value), + simple=1, + ) + ) + elif member_value is not None: + constants.append( + ast.AnnAssign( + target=ast.Name(id=member_name, ctx=ast.Store()), + annotation=concatenated_path_to_type( + member_value.__class__.__name__, element_path, types_to_import + ), + value=ast.Ellipsis(), + simple=1, + ) + ) + else: + logging.warning( + f"Unsupported member {member_name} of class {'.'.join(element_path)}" + ) + + doc = inspect.getdoc(cls_def) + doc_comment = build_doc_comment(doc) if doc else None + return ast.ClassDef( + cls_name, + bases=[], + keywords=[], + body=( + ([doc_comment] if doc_comment else []) + + attributes + + methods + + magic_methods + + constants + ) + or [ast.Ellipsis()], + decorator_list=[path_to_type("typing", "final")], + ) + + +def data_descriptor_stub( + data_desc_name: str, + data_desc_def: Any, + element_path: List[str], + types_to_import: Set[str], +) -> Union[Tuple[ast.AnnAssign, ast.Expr], Tuple[ast.AnnAssign]]: + annotation = None + doc_comment = None + + doc = inspect.getdoc(data_desc_def) + if doc is not None: + annotation = returns_stub(data_desc_name, doc, element_path, types_to_import) + m = re.findall(r"^ *:return: *(.*) *$", doc, re.MULTILINE) + if len(m) == 1: + doc_comment = m[0] + elif len(m) > 1: + raise ValueError( + f"Multiple return annotations found with :return: in {'.'.join(element_path)} documentation" + ) + + assign = ast.AnnAssign( + target=ast.Name(id=data_desc_name, ctx=ast.Store()), + annotation=annotation or path_to_type("typing", "Any"), + simple=1, + ) + doc_comment = build_doc_comment(doc_comment) if doc_comment else None + return (assign, doc_comment) if doc_comment else (assign,) + + +def function_stub( + fn_name: str, + fn_def: Any, + element_path: List[str], + types_to_import: Set[str], + *, + in_class: bool, +) -> ast.FunctionDef: + body: List[ast.AST] = [] + doc = inspect.getdoc(fn_def) + if doc is not None: + doc_comment = build_doc_comment(doc) + if doc_comment is not None: + body.append(doc_comment) + + decorator_list = [] + if in_class and hasattr(fn_def, "__self__"): + decorator_list.append(ast.Name("staticmethod")) + + print(f"Documenting {fn_name}") + + return ast.FunctionDef( + fn_name, + arguments_stub(fn_name, fn_def, doc or "", element_path, types_to_import), + body or [ast.Ellipsis()], + decorator_list=decorator_list, + returns=( + returns_stub(fn_name, doc, element_path, types_to_import) if doc else None + ), + lineno=0, + ) + + +def arguments_stub( + callable_name: str, + callable_def: Any, + doc: str, + element_path: List[str], + types_to_import: Set[str], +) -> ast.arguments: + if "Error" in element_path[1]: + # Don't document errors + return ast.arguments(posonlyargs=[], args=[], defaults=[], kwonlyargs=[]) + + real_parameters: Mapping[str, inspect.Parameter] = inspect.signature( + callable_def + ).parameters + + if callable_name == "__init__": + real_parameters = { + "self": inspect.Parameter("self", inspect.Parameter.POSITIONAL_ONLY), + **real_parameters, + } + + parsed_param_types = {} + optional_params = set() + + # Types for magic functions types + builtin = BUILTINS.get(callable_name) + if isinstance(builtin, tuple): + param_names = list(real_parameters.keys()) + if param_names and param_names[0] == "self": + del param_names[0] + for name, t in zip(param_names, builtin[0]): + parsed_param_types[name] = t + + elif callable_name in ["__add__", "__sub__", "__div__", "__mul__", "__radd__", "__rsub__", "__rdiv__", "__rmul__"]: + return ast.arguments(posonlyargs=[], args=[], defaults=[], kwonlyargs=[]) + + # Types from comment + for match in re.findall( + r"^ *:type *([a-zA-Z0-9_]+): ([^\n]*) *$", doc, re.MULTILINE + ): + if match[0] not in real_parameters: + raise ValueError( + f"The parameter {match[0]} of {'.'.join(element_path)} " + "is defined in the documentation but not in the function signature" + ) + type = match[1] + if type.endswith(", optional"): + optional_params.add(match[0]) + type = type[:-10] + parsed_param_types[match[0]] = convert_type_from_doc( + type, element_path, types_to_import + ) + + # we parse the parameters + posonlyargs = [] + args = [] + vararg = None + kwonlyargs = [] + kw_defaults = [] + kwarg = None + defaults = [] + for param in real_parameters.values(): + if param.name != "self" and param.name not in parsed_param_types: + raise ValueError( + f"The parameter {param.name} of {'.'.join(element_path)} " + "has no type definition in the function documentation" + ) + param_ast = ast.arg( + arg=param.name, annotation=parsed_param_types.get(param.name) + ) + + default_ast = None + if param.default != param.empty: + default_ast = ast.Constant(param.default) + if param.name not in optional_params: + raise ValueError( + f"Parameter {param.name} of {'.'.join(element_path)} " + "is optional according to the type but not flagged as such in the doc" + ) + elif param.name in optional_params: + raise ValueError( + f"Parameter {param.name} of {'.'.join(element_path)} " + "is optional according to the documentation but has no default value" + ) + + if param.kind == param.POSITIONAL_ONLY: + args.append(param_ast) + # posonlyargs.append(param_ast) + # defaults.append(default_ast) + elif param.kind == param.POSITIONAL_OR_KEYWORD: + args.append(param_ast) + defaults.append(default_ast) + elif param.kind == param.VAR_POSITIONAL: + vararg = param_ast + elif param.kind == param.KEYWORD_ONLY: + kwonlyargs.append(param_ast) + kw_defaults.append(default_ast) + elif param.kind == param.VAR_KEYWORD: + kwarg = param_ast + + return ast.arguments( + posonlyargs=posonlyargs, + args=args, + vararg=vararg, + kwonlyargs=kwonlyargs, + kw_defaults=kw_defaults, + defaults=defaults, + kwarg=kwarg, + ) + + +def returns_stub( + callable_name: str, doc: str, element_path: List[str], types_to_import: Set[str] +) -> Optional[ast.AST]: + if "Error" in element_path[1]: + # Don't document errors + return + + if callable_name in ["__add__", "__sub__", "__div__", "__mul__", "__radd__", "__rsub__", "__rdiv__", "__rmul__"]: + return + m = re.findall(r"^ *:rtype: *([^\n]*) *$", doc, re.MULTILINE) + if len(m) == 0: + builtin = BUILTINS.get(callable_name) + if isinstance(builtin, tuple) and builtin[1] is not None: + return builtin[1] + raise ValueError( + f"The return type of {'.'.join(element_path)} " + "has no type definition using :rtype: in the function documentation" + ) + if len(m) > 1: + raise ValueError( + f"Multiple return type annotations found with :rtype: for {'.'.join(element_path)}" + ) + return convert_type_from_doc(m[0], element_path, types_to_import) + + +def convert_type_from_doc( + type_str: str, element_path: List[str], types_to_import: Set[str] +) -> ast.AST: + type_str = type_str.strip() + return parse_type_to_ast(type_str, element_path, types_to_import) + + +def parse_type_to_ast( + type_str: str, element_path: List[str], types_to_import: Set[str] +) -> ast.AST: + # let's tokenize + tokens = [] + current_token = "" + for c in type_str: + if "a" <= c <= "z" or "A" <= c <= "Z" or c == ".": + current_token += c + else: + if current_token: + tokens.append(current_token) + current_token = "" + if c != " ": + tokens.append(c) + if current_token: + tokens.append(current_token) + + # let's first parse nested parenthesis + stack: List[List[Any]] = [[]] + for token in tokens: + if token == "[": + children: List[str] = [] + stack[-1].append(children) + stack.append(children) + elif token == "]": + stack.pop() + else: + stack[-1].append(token) + + # then it's easy + def parse_sequence(sequence: List[Any]) -> ast.AST: + # we split based on "or" + or_groups: List[List[str]] = [[]] + print(sequence) + # TODO: Fix sequence + if "Ros" in sequence and "2" in sequence: + sequence = ["".join(sequence)] + elif "dora.Ros" in sequence and "2" in sequence: + sequence = ["".join(sequence)] + + for e in sequence: + if e == "or": + or_groups.append([]) + else: + or_groups[-1].append(e) + if any(not g for g in or_groups): + raise ValueError( + f"Not able to parse type '{type_str}' used by {'.'.join(element_path)}" + ) + + new_elements: List[ast.AST] = [] + for group in or_groups: + if len(group) == 1 and isinstance(group[0], str): + new_elements.append( + concatenated_path_to_type(group[0], element_path, types_to_import) + ) + elif ( + len(group) == 2 + and isinstance(group[0], str) + and isinstance(group[1], list) + ): + new_elements.append( + ast.Subscript( + value=concatenated_path_to_type( + group[0], element_path, types_to_import + ), + slice=parse_sequence(group[1]), + ctx=ast.Load(), + ) + ) + else: + raise ValueError( + f"Not able to parse type '{type_str}' used by {'.'.join(element_path)}" + ) + return reduce( + lambda left, right: ast.BinOp(left=left, op=ast.BitOr(), right=right), + new_elements, + ) + + return parse_sequence(stack[0]) + + +def concatenated_path_to_type( + path: str, element_path: List[str], types_to_import: Set[str] +) -> ast.AST: + parts = path.split(".") + if any(not p for p in parts): + raise ValueError( + f"Not able to parse type '{path}' used by {'.'.join(element_path)}" + ) + if len(parts) > 1: + types_to_import.add(".".join(parts[:-1])) + return path_to_type(*parts) + + +def build_doc_comment(doc: str) -> Optional[ast.Expr]: + lines = [line.strip() for line in doc.split("\n")] + clean_lines = [] + for line in lines: + if line.startswith((":type", ":rtype")): + continue + clean_lines.append(line) + text = "\n".join(clean_lines).strip() + return ast.Expr(value=ast.Constant(text)) if text else None + + +def format_with_ruff(file: str) -> None: + subprocess.check_call(["python", "-m", "ruff", "format", file]) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Extract Python type stub from a python module." + ) + parser.add_argument( + "module_name", help="Name of the Python module for which generate stubs" + ) + parser.add_argument( + "out", + help="Name of the Python stub file to write to", + type=argparse.FileType("wt"), + ) + parser.add_argument( + "--ruff", help="Formats the generated stubs using Ruff", action="store_true" + ) + args = parser.parse_args() + stub_content = ast.unparse(module_stubs(importlib.import_module(args.module_name))) + args.out.write(stub_content) + if args.ruff: + format_with_ruff(args.out.name) diff --git a/anise/src/almanac/metaload/metaalmanac.rs b/anise/src/almanac/metaload/metaalmanac.rs index 5c150a45..95cea437 100644 --- a/anise/src/almanac/metaload/metaalmanac.rs +++ b/anise/src/almanac/metaload/metaalmanac.rs @@ -37,7 +37,6 @@ use super::{Almanac, MetaAlmanacError, MetaFile}; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[cfg_attr(feature = "python", pyclass)] #[cfg_attr(feature = "python", pyo3(module = "anise"))] -#[cfg_attr(feature = "python", pyo3(get_all, set_all))] pub struct MetaAlmanac { pub files: Vec, } @@ -207,6 +206,18 @@ impl MetaAlmanac { ))), } } + + /// :rtype: typing.List + #[getter] + fn get_files(&self) -> PyResult> { + Ok(self.files.clone()) + } + /// :type files: typing.List + #[setter] + fn set_files(&mut self, files: Vec) -> PyResult<()> { + self.files = files; + Ok(()) + } } /// By default, the MetaAlmanac will download the DE440s.bsp file, the PCK0008.PCA, the full Moon Principal Axis BPC (moon_pa_de440_200625) and the latest high precision Earth kernel from JPL. diff --git a/anise/src/almanac/metaload/metafile.rs b/anise/src/almanac/metaload/metafile.rs index 548e15b3..45c12915 100644 --- a/anise/src/almanac/metaload/metafile.rs +++ b/anise/src/almanac/metaload/metafile.rs @@ -40,7 +40,6 @@ use super::MetaAlmanacError; /// then the file will not be downloaded a second time. #[cfg_attr(feature = "python", pyclass)] #[cfg_attr(feature = "python", pyo3(module = "anise"))] -#[cfg_attr(feature = "python", pyo3(get_all, set_all))] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, StaticType)] pub struct MetaFile { /// URI of this meta file @@ -297,6 +296,29 @@ impl MetaFile { ) -> Result<(), MetaAlmanacError> { py.allow_threads(|| self._process(autodelete.unwrap_or(false))) } + + /// :rtype: str + #[getter] + fn get_uri(&self) -> PyResult { + Ok(self.uri.clone()) + } + /// :type uri: str + #[setter] + fn set_uri(&mut self, uri: String) -> PyResult<()> { + self.uri = uri; + Ok(()) + } + /// :rtype: int + #[getter] + fn get_crc32(&self) -> PyResult> { + Ok(self.crc32) + } + /// :type crc32: int + #[setter] + fn set_crc32(&mut self, crc32: Option) -> PyResult<()> { + self.crc32 = crc32; + Ok(()) + } } fn replace_env_vars(input: &str) -> String { diff --git a/anise/src/astro/aberration.rs b/anise/src/astro/aberration.rs index 259bf7b8..096e0dfc 100644 --- a/anise/src/astro/aberration.rs +++ b/anise/src/astro/aberration.rs @@ -40,10 +40,13 @@ use crate::errors::PhysicsError; /// /// The validation test `validate_jplde_de440s_aberration_lt` checks 101,000 pairs of ephemeris computations and shows that the unconverged Light Time computation matches the SPICE computations almost all the time. /// More specifically, the 99th percentile of error is less than 5 meters, the 75th percentile is less than one meter, and the median error is less than 2 millimeters. +/// +/// # Python docstring +/// :type name: str +/// :rtype: Aberration #[derive(Copy, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "python", pyclass)] #[cfg_attr(feature = "python", pyo3(module = "anise"))] -#[cfg_attr(feature = "python", pyo3(get_all, set_all))] pub struct Aberration { /// Indicates whether the light time calculations should be iterated upon (more precise but three times as many CPU cycles). pub converged: bool, @@ -167,6 +170,43 @@ impl Aberration { fn __repr__(&self) -> String { format!("{self:?} (@{self:p})") } + + // Manual getters and setters for the stubs + + /// :rtype: bool + #[getter] + fn get_converged(&self) -> PyResult { + Ok(self.converged) + } + + /// :type converged: bool + #[setter] + fn set_converged(&mut self, converged: bool) -> PyResult<()> { + self.converged = converged; + Ok(()) + } + /// :rtype: bool + #[getter] + fn get_stellar(&self) -> PyResult { + Ok(self.stellar) + } + /// :type stellar: bool + #[setter] + fn set_stellar(&mut self, stellar: bool) -> PyResult<()> { + self.stellar = stellar; + Ok(()) + } + /// :rtype: bool + #[getter] + fn get_transmit_mode(&self) -> PyResult { + Ok(self.transmit_mode) + } + /// :type transmit_mode: bool + #[setter] + fn set_transmit_mode(&mut self, transmit_mode: bool) -> PyResult<()> { + self.transmit_mode = transmit_mode; + Ok(()) + } } impl fmt::Debug for Aberration { diff --git a/anise/src/frames/frame.rs b/anise/src/frames/frame.rs index d38c65c4..87b1dd86 100644 --- a/anise/src/frames/frame.rs +++ b/anise/src/frames/frame.rs @@ -37,7 +37,6 @@ use pyo3::pyclass::CompareOp; #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] #[cfg_attr(feature = "metaload", derive(StaticType))] #[cfg_attr(feature = "python", pyclass)] -#[cfg_attr(feature = "python", pyo3(get_all, set_all))] #[cfg_attr(feature = "python", pyo3(module = "anise.astro"))] pub struct Frame { pub ephemeris_id: NaifId, @@ -88,10 +87,10 @@ impl Frame { } } +#[cfg(feature = "python")] #[cfg_attr(feature = "python", pymethods)] impl Frame { /// Initializes a new [Frame] provided its ephemeris and orientation identifiers, and optionally its gravitational parameter (in km^3/s^2) and optionally its shape (cf. [Ellipsoid]). - #[cfg(feature = "python")] #[new] pub fn py_new( ephemeris_id: NaifId, @@ -107,17 +106,14 @@ impl Frame { } } - #[cfg(feature = "python")] fn __str__(&self) -> String { format!("{self}") } - #[cfg(feature = "python")] fn __repr__(&self) -> String { format!("{self} (@{self:p})") } - #[cfg(feature = "python")] fn __richcmp__(&self, other: &Self, op: CompareOp) -> Result { match op { CompareOp::Eq => Ok(self == other), @@ -129,7 +125,6 @@ impl Frame { } /// Allows for pickling the object - #[cfg(feature = "python")] fn __getnewargs__(&self) -> Result<(NaifId, NaifId, Option, Option), PyErr> { Ok(( self.ephemeris_id, @@ -139,6 +134,54 @@ impl Frame { )) } + /// :rtype: int + #[getter] + fn get_ephemeris_id(&self) -> PyResult { + Ok(self.ephemeris_id) + } + /// :type ephemeris_id: int + #[setter] + fn set_ephemeris_id(&mut self, ephemeris_id: NaifId) -> PyResult<()> { + self.ephemeris_id = ephemeris_id; + Ok(()) + } + /// :rtype: int + #[getter] + fn get_orientation_id(&self) -> PyResult { + Ok(self.orientation_id) + } + /// :type orientation_id: int + #[setter] + fn set_orientation_id(&mut self, orientation_id: NaifId) -> PyResult<()> { + self.orientation_id = orientation_id; + Ok(()) + } + /// :rtype: float + #[getter] + fn get_mu_km3_s2(&self) -> PyResult> { + Ok(self.mu_km3_s2) + } + /// :type mu_km3_s2: float + #[setter] + fn set_mu_km3_s2(&mut self, mu_km3_s2: Option) -> PyResult<()> { + self.mu_km3_s2 = mu_km3_s2; + Ok(()) + } + /// :rtype: Ellipsoid + #[getter] + fn get_shape(&self) -> PyResult> { + Ok(self.shape) + } + /// :type shape: Ellipsoid + #[setter] + fn set_shape(&mut self, shape: Option) -> PyResult<()> { + self.shape = shape; + Ok(()) + } +} + +#[cfg_attr(feature = "python", pymethods)] +impl Frame { /// Returns a copy of this Frame whose ephemeris ID is set to the provided ID pub const fn with_ephem(&self, new_ephem_id: NaifId) -> Self { let mut me = *self; diff --git a/anise/src/structure/planetocentric/ellipsoid.rs b/anise/src/structure/planetocentric/ellipsoid.rs index 00156d41..404dd36c 100644 --- a/anise/src/structure/planetocentric/ellipsoid.rs +++ b/anise/src/structure/planetocentric/ellipsoid.rs @@ -35,7 +35,6 @@ use pyo3::pyclass::CompareOp; #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] #[cfg_attr(feature = "metaload", derive(StaticType))] #[cfg_attr(feature = "python", pyclass)] -#[cfg_attr(feature = "python", pyo3(get_all, set_all))] #[cfg_attr(feature = "python", pyo3(module = "anise.astro"))] pub struct Ellipsoid { pub semi_major_equatorial_radius_km: f64, @@ -64,11 +63,11 @@ impl Ellipsoid { } #[cfg_attr(feature = "python", pymethods)] +#[cfg(feature = "python")] impl Ellipsoid { /// Initializes a new [Ellipsoid] shape provided at least its semi major equatorial radius, optionally its semi minor equatorial radius, and optionally its polar radius. /// All units are in kilometers. If the semi minor equatorial radius is not provided, a bi-axial spheroid will be created using the semi major equatorial radius as /// the equatorial radius and using the provided polar axis radius. If only the semi major equatorial radius is provided, a perfect sphere will be built. - #[cfg(feature = "python")] #[new] fn py_new( semi_major_equatorial_radius_km: f64, @@ -88,17 +87,14 @@ impl Ellipsoid { } } - #[cfg(feature = "python")] fn __str__(&self) -> String { format!("{self}") } - #[cfg(feature = "python")] fn __repr__(&self) -> String { format!("{self} (@{self:p})") } - #[cfg(feature = "python")] fn __richcmp__(&self, other: &Self, op: CompareOp) -> Result { match op { CompareOp::Eq => Ok(self == other), @@ -110,7 +106,6 @@ impl Ellipsoid { } /// Allows for pickling the object - #[cfg(feature = "python")] fn __getnewargs__(&self) -> Result<(f64, Option, Option), PyErr> { Ok(( self.semi_major_equatorial_radius_km, @@ -119,6 +114,49 @@ impl Ellipsoid { )) } + /// :rtype: float + #[getter] + fn get_semi_major_equatorial_radius_km(&self) -> PyResult { + Ok(self.semi_major_equatorial_radius_km) + } + /// :type semi_major_equatorial_radius_km: float + #[setter] + fn set_semi_major_equatorial_radius_km( + &mut self, + semi_major_equatorial_radius_km: f64, + ) -> PyResult<()> { + self.semi_major_equatorial_radius_km = semi_major_equatorial_radius_km; + Ok(()) + } + /// :rtype: float + #[getter] + fn get_polar_radius_km(&self) -> PyResult { + Ok(self.polar_radius_km) + } + /// :type polar_radius_km: float + #[setter] + fn set_polar_radius_km(&mut self, polar_radius_km: f64) -> PyResult<()> { + self.polar_radius_km = polar_radius_km; + Ok(()) + } + /// :rtype: float + #[getter] + fn get_semi_minor_equatorial_radius_km(&self) -> PyResult { + Ok(self.semi_minor_equatorial_radius_km) + } + /// :type semi_minor_equatorial_radius_km: float + #[setter] + fn set_semi_minor_equatorial_radius_km( + &mut self, + semi_minor_equatorial_radius_km: f64, + ) -> PyResult<()> { + self.semi_minor_equatorial_radius_km = semi_minor_equatorial_radius_km; + Ok(()) + } +} + +#[cfg_attr(feature = "python", pymethods)] +impl Ellipsoid { /// Returns the mean equatorial radius in kilometers pub fn mean_equatorial_radius_km(&self) -> f64 { (self.semi_major_equatorial_radius_km + self.semi_minor_equatorial_radius_km) / 2.0 From 6c18d42d91be9eb56c3d195d54267fa7b3824aaa Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Fri, 1 Nov 2024 18:30:28 -0600 Subject: [PATCH 2/6] anise.pyi built, but it's missing submodules --- anise-py/anise.pyi | 407 +++++++++++++++++++ anise/src/almanac/aer.rs | 5 + anise/src/almanac/bpc.rs | 11 + anise/src/almanac/eclipse.rs | 19 +- anise/src/almanac/metaload/metaalmanac.rs | 16 +- anise/src/almanac/metaload/metafile.rs | 7 + anise/src/almanac/metaload/mod.rs | 11 +- anise/src/almanac/mod.rs | 16 +- anise/src/almanac/python.rs | 3 + anise/src/almanac/solar.rs | 10 + anise/src/almanac/spk.rs | 8 + anise/src/almanac/transform.rs | 23 ++ anise/src/astro/aberration.rs | 3 +- anise/src/ephemerides/translate_to_parent.rs | 4 + anise/src/ephemerides/translations.rs | 16 + 15 files changed, 551 insertions(+), 8 deletions(-) diff --git a/anise-py/anise.pyi b/anise-py/anise.pyi index e69de29b..2dd10796 100644 --- a/anise-py/anise.pyi +++ b/anise-py/anise.pyi @@ -0,0 +1,407 @@ +import typing + +@typing.final +class Aberration: + """Represents the aberration correction options in ANISE. + +In space science and engineering, accurately pointing instruments (like optical cameras or radio antennas) at a target is crucial. This task is complicated by the finite speed of light, necessitating corrections for the apparent position of the target. + +This structure holds parameters for aberration corrections applied to a target's position or state vector. These corrections account for the difference between the target's geometric (true) position and its apparent position as observed. + +# Rule of tumb +In most Earth orbits, one does _not_ need to provide any aberration corrections. Light time to the target is less than one second (the Moon is about one second away). +In near Earth orbits, e.g. inner solar system, preliminary analysis can benefit from enabling unconverged light time correction. Stellar aberration is probably not required. +For deep space missions, preliminary analysis would likely require both light time correction and stellar aberration. Mission planning and operations will definitely need converged light-time calculations. + +For more details, . + +# SPICE Validation + +The validation test `validate_jplde_de440s_aberration_lt` checks 101,000 pairs of ephemeris computations and shows that the unconverged Light Time computation matches the SPICE computations almost all the time. +More specifically, the 99th percentile of error is less than 5 meters, the 75th percentile is less than one meter, and the median error is less than 2 millimeters. + +# Python docstring""" + converged: bool + stellar: bool + transmit_mode: bool + + def __init__(self, name: str) -> Aberration: + """Represents the aberration correction options in ANISE. + +In space science and engineering, accurately pointing instruments (like optical cameras or radio antennas) at a target is crucial. This task is complicated by the finite speed of light, necessitating corrections for the apparent position of the target. + +This structure holds parameters for aberration corrections applied to a target's position or state vector. These corrections account for the difference between the target's geometric (true) position and its apparent position as observed. + +# Rule of tumb +In most Earth orbits, one does _not_ need to provide any aberration corrections. Light time to the target is less than one second (the Moon is about one second away). +In near Earth orbits, e.g. inner solar system, preliminary analysis can benefit from enabling unconverged light time correction. Stellar aberration is probably not required. +For deep space missions, preliminary analysis would likely require both light time correction and stellar aberration. Mission planning and operations will definitely need converged light-time calculations. + +For more details, . + +# SPICE Validation + +The validation test `validate_jplde_de440s_aberration_lt` checks 101,000 pairs of ephemeris computations and shows that the unconverged Light Time computation matches the SPICE computations almost all the time. +More specifically, the 99th percentile of error is less than 5 meters, the 75th percentile is less than one meter, and the median error is less than 2 millimeters. + +# Python docstring""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + +@typing.final +class Almanac: + """An Almanac contains all of the loaded SPICE and ANISE data. It is the context for all computations.""" + + def __init__(self, path: str) -> Almanac: + """An Almanac contains all of the loaded SPICE and ANISE data. It is the context for all computations.""" + + def azimuth_elevation_range_sez(self, rx: Orbit, tx: Orbit, obstructing_body: Frame=None, ab_corr: Aberration=None) -> AzElRange: + """Computes the azimuth (in degrees), elevation (in degrees), and range (in kilometers) of the +receiver state (`rx`) seen from the transmitter state (`tx`), once converted into the SEZ frame of the transmitter. + +# Warning +The obstructing body _should_ be a tri-axial ellipsoid body, e.g. IAU_MOON_FRAME. + +# Algorithm +1. If any obstructing_bodies are provided, ensure that none of these are obstructing the line of sight between the receiver and transmitter. +2. Compute the SEZ (South East Zenith) frame of the transmitter. +3. Rotate the receiver position vector into the transmitter SEZ frame. +4. Rotate the transmitter position vector into that same SEZ frame. +5. Compute the range as the norm of the difference between these two position vectors. +6. Compute the elevation, and ensure it is between +/- 180 degrees. +7. Compute the azimuth with a quadrant check, and ensure it is between 0 and 360 degrees.""" + + def bpc_domain(self, id: int) -> typing.Tuple: + """Returns the applicable domain of the request id, i.e. start and end epoch that the provided id has loaded data.""" + + def bpc_domains(self) -> typing.Dict: + """Returns a map of each loaded BPC ID to its domain validity. + +# Warning +This function performs a memory allocation.""" + + def bpc_summaries(self, id: int) -> typing.List: + """Returns a vector of the summaries whose ID matches the desired `id`, in the order in which they will be used, i.e. in reverse loading order. + +# Warning +This function performs a memory allocation.""" + + def describe(self, spk: bool=None, bpc: bool=None, planetary: bool=None, time_scale: TimeScale=None, round_time: bool=None) -> None: + """Pretty prints the description of this Almanac, showing everything by default. Default time scale is TDB. +If any parameter is set to true, then nothing other than that will be printed.""" + + def frame_info(self, uid: Frame) -> Frame: + """Returns the frame information (gravitational param, shape) as defined in this Almanac from an empty frame""" + + def line_of_sight_obstructed(self, observer: Orbit, observed: Orbit, obstructing_body: Frame, ab_corr: Aberration=None) -> bool: + """Computes whether the line of sight between an observer and an observed Cartesian state is obstructed by the obstructing body. +Returns true if the obstructing body is in the way, false otherwise. + +For example, if the Moon is in between a Lunar orbiter (observed) and a ground station (observer), then this function returns `true` +because the Moon (obstructing body) is indeed obstructing the line of sight. + +```text +Observed +o - ++ - ++ - ++ *** - +* + * - +* + + * + + o +* * Observer +**** +``` + +Key Elements: +- `o` represents the positions of the observer and observed objects. +- The dashed line connecting the observer and observed is the line of sight. + +Algorithm (source: Algorithm 35 of Vallado, 4th edition, page 308.): +- `r1` and `r2` are the transformed radii of the observed and observer objects, respectively. +- `r1sq` and `r2sq` are the squared magnitudes of these vectors. +- `r1dotr2` is the dot product of `r1` and `r2`. +- `tau` is a parameter that determines the intersection point along the line of sight. +- The condition `(1.0 - tau) * r1sq + r1dotr2 * tau <= ob_mean_eq_radius_km^2` checks if the line of sight is within the obstructing body's radius, indicating an obstruction.""" + + def load(self, path: str) -> Almanac: + """Generic function that tries to load the provided path guessing to the file type.""" + + def load_from_metafile(self, metafile: Metafile, autodelete: bool) -> Almanac: + """Load from the provided MetaFile, downloading it if necessary. +Set autodelete to true to automatically delete lock files. Lock files are important in multi-threaded loads.""" + + def occultation(self, back_frame: Frame, front_frame: Frame, observer: Orbit, ab_corr: Aberration=None) -> Occultation: + """Computes the occultation percentage of the `back_frame` object by the `front_frame` object as seen from the observer, when according for the provided aberration correction. + +A zero percent occultation means that the back object is fully visible from the observer. +A 100% percent occultation means that the back object is fully hidden from the observer because of the front frame (i.e. _umbra_ if the back object is the Sun). +A value in between means that the back object is partially hidden from the observser (i.e. _penumbra_ if the back object is the Sun). +Refer to the [MathSpec](https://nyxspace.com/nyxspace/MathSpec/celestial/eclipse/) for modeling details.""" + + def solar_eclipsing(self, eclipsing_frame: Frame, observer: Orbit, ab_corr: Aberration=None) -> Occultation: + """Computes the solar eclipsing of the observer due to the eclipsing_frame. + +This function calls `occultation` where the back object is the Sun in the J2000 frame, and the front object +is the provided eclipsing frame.""" + + def spk_domain(self, id: int) -> typing.Tuple: + """Returns the applicable domain of the request id, i.e. start and end epoch that the provided id has loaded data.""" + + def spk_domains(self) -> typing.Dict: + """Returns a map of each loaded SPK ID to its domain validity. + +# Warning +This function performs a memory allocation.""" + + def spk_ezr(self, target: int, epoch: Epoch, frame: int, observer: int, ab_corr: Aberration=None) -> Orbit: + """Alias fo SPICE's `spkezr` where the inputs must be the NAIF IDs of the objects and frames with the caveat that the aberration is moved to the last positional argument.""" + + def spk_summaries(self, id: int) -> typing.List: + """Returns a vector of the summaries whose ID matches the desired `id`, in the order in which they will be used, i.e. in reverse loading order. + +# Warning +This function performs a memory allocation.""" + + def state_of(self, object: int, observer: Frame, epoch: Epoch, ab_corr: Aberration=None) -> Orbit: + """Returns the Cartesian state of the object as seen from the provided observer frame (essentially `spkezr`). + +# Note +The units will be those of the underlying ephemeris data (typically km and km/s)""" + + def sun_angle_deg(self, target_id: int, observer_id: int, epoch: Epoch) -> float: + """Returns the angle (between 0 and 180 degrees) between the observer and the Sun, and the observer and the target body ID. +This computes the Sun Probe Earth angle (SPE) if the probe is in a loaded SPK, its ID is the "observer_id", and the target is set to its central body. + +# Geometry +If the SPE is greater than 90 degrees, then the celestial object below the probe is in sunlight. + +## Sunrise at nadir +```text +Sun +| \\ +| \\ +| \\ +Obs. -- Target +``` +## Sun high at nadir +```text +Sun +\\ +\\ __ θ > 90 +\\ \\ +Obs. ---------- Target +``` + +## Sunset at nadir +```text +Sun +/ +/ __ θ < 90 +/ / +Obs. -- Target +``` + +# Algorithm +1. Compute the position of the Sun as seen from the observer +2. Compute the position of the target as seen from the observer +3. Return the arccosine of the dot product of the norms of these vectors.""" + + def sun_angle_deg_from_frame(self, target: Frame, observer: Frame, epoch: Epoch) -> float: + """Convenience function that calls `sun_angle_deg` with the provided frames instead of the ephemeris ID.""" + + def transform(self, target_frame: Orbit, observer_frame: Frame, epoch: Epoch, ab_corr: Aberration=None) -> Orbit: + """Returns the Cartesian state needed to transform the `from_frame` to the `to_frame`. + +# SPICE Compatibility +This function is the SPICE equivalent of spkezr: `spkezr(TARGET_ID, EPOCH_TDB_S, ORIENTATION_ID, ABERRATION, OBSERVER_ID)` +In ANISE, the TARGET_ID and ORIENTATION are provided in the first argument (TARGET_FRAME), as that frame includes BOTH +the target ID and the orientation of that target. The EPOCH_TDB_S is the epoch in the TDB time system, which is computed +in ANISE using Hifitime. THe ABERRATION is computed by providing the optional Aberration flag. Finally, the OBSERVER +argument is replaced by OBSERVER_FRAME: if the OBSERVER_FRAME argument has the same orientation as the TARGET_FRAME, then this call +will return exactly the same data as the spkerz SPICE call. + +# Note +The units will be those of the underlying ephemeris data (typically km and km/s)""" + + def transform_to(self, state: Orbit, observer_frame: Frame, ab_corr: Aberration=None) -> Orbit: + """Translates a state with its origin (`to_frame`) and given its units (distance_unit, time_unit), returns that state with respect to the requested frame + +**WARNING:** This function only performs the translation and no rotation _whatsoever_. Use the `transform_state_to` function instead to include rotations.""" + + def translate(self, target_frame: Orbit, observer_frame: Frame, epoch: Epoch, ab_corr: Aberration=None) -> Orbit: + """Returns the Cartesian state of the target frame as seen from the observer frame at the provided epoch, and optionally given the aberration correction. + +# SPICE Compatibility +This function is the SPICE equivalent of spkezr: `spkezr(TARGET_ID, EPOCH_TDB_S, ORIENTATION_ID, ABERRATION, OBSERVER_ID)` +In ANISE, the TARGET_ID and ORIENTATION are provided in the first argument (TARGET_FRAME), as that frame includes BOTH +the target ID and the orientation of that target. The EPOCH_TDB_S is the epoch in the TDB time system, which is computed +in ANISE using Hifitime. THe ABERRATION is computed by providing the optional Aberration flag. Finally, the OBSERVER +argument is replaced by OBSERVER_FRAME: if the OBSERVER_FRAME argument has the same orientation as the TARGET_FRAME, then this call +will return exactly the same data as the spkerz SPICE call. + +# Warning +This function only performs the translation and no rotation whatsoever. Use the `transform` function instead to include rotations. + +# Note +This function performs a recursion of no more than twice the [MAX_TREE_DEPTH].""" + + def translate_geometric(self, target_frame: Orbit, observer_frame: Frame, epoch: Epoch) -> Orbit: + """Returns the geometric position vector, velocity vector, and acceleration vector needed to translate the `from_frame` to the `to_frame`, where the distance is in km, the velocity in km/s, and the acceleration in km/s^2.""" + + def translate_to(self, state: Orbit, observer_frame: Frame, ab_corr: Aberration=None) -> Orbit: + """Translates the provided Cartesian state into the requested observer frame + +**WARNING:** This function only performs the translation and no rotation _whatsoever_. Use the `transform_to` function instead to include rotations.""" + + def translate_to_parent(self, source: Frame, epoch: Epoch) -> Orbit: + """Performs the GEOMETRIC translation to the parent. Use translate_from_to for aberration.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + +@typing.final +class MetaAlmanac: + """A structure to set up an Almanac, with automatic downloading, local storage, checksum checking, and more. + +# Behavior +If the URI is a local path, relative or absolute, nothing will be fetched from a remote. Relative paths are relative to the execution folder (i.e. the current working directory). +If the URI is a remote path, the MetaAlmanac will first check if the file exists locally. If it exists, it will check that the CRC32 checksum of this file matches that of the specs. +If it does not match, the file will be downloaded again. If no CRC32 is provided but the file exists, then the MetaAlmanac will fetch the remote file and overwrite the existing file. +The downloaded path will be stored in the "AppData" folder.""" + files: typing.List + + def __init__(self, maybe_path: str=None) -> MetaAlmanac: + """A structure to set up an Almanac, with automatic downloading, local storage, checksum checking, and more. + +# Behavior +If the URI is a local path, relative or absolute, nothing will be fetched from a remote. Relative paths are relative to the execution folder (i.e. the current working directory). +If the URI is a remote path, the MetaAlmanac will first check if the file exists locally. If it exists, it will check that the CRC32 checksum of this file matches that of the specs. +If it does not match, the file will be downloaded again. If no CRC32 is provided but the file exists, then the MetaAlmanac will fetch the remote file and overwrite the existing file. +The downloaded path will be stored in the "AppData" folder.""" + + def dumps(self) -> str: + """Dumps the configured Meta Almanac into a Dhall string.""" + + @staticmethod + def latest(autodelete: bool=None) -> MetaAlmanac: + """Returns an Almanac loaded from the latest NAIF data via the `default` MetaAlmanac. +The MetaAlmanac will download the DE440s.bsp file, the PCK0008.PCA, the full Moon Principal Axis BPC (moon_pa_de440_200625) and the latest high precision Earth kernel from JPL. + +# File list +- +- +- +- + +# Reproducibility + +Note that the `earth_latest_high_prec.bpc` file is regularly updated daily (or so). As such, +if queried at some future time, the Earth rotation parameters may have changed between two queries. + +Set `autodelete` to true to delete lock file if a dead lock is detected after 10 seconds.""" + + @staticmethod + def loads(s: str) -> MetaAlmanac: + """Loads the provided string as a Dhall configuration to build a MetaAlmanac""" + + def process(self, autodelete: bool=None) -> Almanac: + """Fetch all of the URIs and return a loaded Almanac. +When downloading the data, ANISE will create a temporarily lock file to prevent race conditions +where multiple processes download the data at the same time. Set `autodelete` to true to delete +this lock file if a dead lock is detected after 10 seconds. Set this flag to false if you have +more than ten processes which may attempt to download files in parallel.""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + +@typing.final +class MetaFile: + """MetaFile allows downloading a remote file from a URL (http, https only), and interpolation of paths in environment variable using the Dhall syntax `env:MY_ENV_VAR`. + +The data is stored in the user's local temp directory (i.e. `~/.local/share/nyx-space/anise/` on Linux and `AppData/Local/nyx-space/anise/` on Windows). +Prior to loading a remote resource, if the local resource exists, its CRC32 will be computed: if it matches the CRC32 of this instance of MetaFile, +then the file will not be downloaded a second time.""" + crc32: int + uri: str + + def __init__(self, uri: str, crc32: int=None) -> MetaFile: + """MetaFile allows downloading a remote file from a URL (http, https only), and interpolation of paths in environment variable using the Dhall syntax `env:MY_ENV_VAR`. + +The data is stored in the user's local temp directory (i.e. `~/.local/share/nyx-space/anise/` on Linux and `AppData/Local/nyx-space/anise/` on Windows). +Prior to loading a remote resource, if the local resource exists, its CRC32 will be computed: if it matches the CRC32 of this instance of MetaFile, +then the file will not be downloaded a second time.""" + + def process(self, autodelete: bool=None) -> None: + """Processes this MetaFile by downloading it if it's a URL. + +This function modified `self` and changes the URI to be the path to the downloaded file.""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" \ No newline at end of file diff --git a/anise/src/almanac/aer.rs b/anise/src/almanac/aer.rs index 81a4950c..e546ebc3 100644 --- a/anise/src/almanac/aer.rs +++ b/anise/src/almanac/aer.rs @@ -47,6 +47,11 @@ impl Almanac { /// 6. Compute the elevation, and ensure it is between +/- 180 degrees. /// 7. Compute the azimuth with a quadrant check, and ensure it is between 0 and 360 degrees. /// + /// :type rx: Orbit + /// :type tx: Orbit + /// :type obstructing_body: Frame, optional + /// :type ab_corr: Aberration, optional + /// :rtype: AzElRange pub fn azimuth_elevation_range_sez( &self, rx: Orbit, diff --git a/anise/src/almanac/bpc.rs b/anise/src/almanac/bpc.rs index aa3fa864..1e96005f 100644 --- a/anise/src/almanac/bpc.rs +++ b/anise/src/almanac/bpc.rs @@ -182,6 +182,12 @@ impl Almanac { #[cfg_attr(feature = "python", pymethods)] impl Almanac { /// Returns a vector of the summaries whose ID matches the desired `id`, in the order in which they will be used, i.e. in reverse loading order. + /// + /// # Warning + /// This function performs a memory allocation. + /// + /// :type id: int + /// :rtype: typing.List pub fn bpc_summaries(&self, id: NaifId) -> Result, OrientationError> { let mut summaries = vec![]; @@ -208,6 +214,9 @@ impl Almanac { } /// Returns the applicable domain of the request id, i.e. start and end epoch that the provided id has loaded data. + /// + /// :type id: int + /// :rtype: typing.Tuple pub fn bpc_domain(&self, id: NaifId) -> Result<(Epoch, Epoch), OrientationError> { let summaries = self.bpc_summaries(id)?; @@ -231,6 +240,8 @@ impl Almanac { /// /// # Warning /// This function performs a memory allocation. + /// + /// :rtype: typing.Dict pub fn bpc_domains(&self) -> Result, OrientationError> { ensure!(self.num_loaded_bpc() > 0, NoOrientationsLoadedSnafu); diff --git a/anise/src/almanac/eclipse.rs b/anise/src/almanac/eclipse.rs index bc42b78a..03b0e819 100644 --- a/anise/src/almanac/eclipse.rs +++ b/anise/src/almanac/eclipse.rs @@ -58,6 +58,11 @@ impl Almanac { /// - `tau` is a parameter that determines the intersection point along the line of sight. /// - The condition `(1.0 - tau) * r1sq + r1dotr2 * tau <= ob_mean_eq_radius_km^2` checks if the line of sight is within the obstructing body's radius, indicating an obstruction. /// + /// :type observer: Orbit + /// :type observed: Orbit + /// :type obstructing_body: Frame + /// :type ab_corr: Aberration, optional + /// :rtype: bool pub fn line_of_sight_obstructed( &self, observer: Orbit, @@ -114,6 +119,12 @@ impl Almanac { /// A 100% percent occultation means that the back object is fully hidden from the observer because of the front frame (i.e. _umbra_ if the back object is the Sun). /// A value in between means that the back object is partially hidden from the observser (i.e. _penumbra_ if the back object is the Sun). /// Refer to the [MathSpec](https://nyxspace.com/nyxspace/MathSpec/celestial/eclipse/) for modeling details. + /// + /// :type back_frame: Frame + /// :type front_frame: Frame + /// :type observer: Orbit + /// :type ab_corr: Aberration, optional + /// :rtype: Occultation pub fn occultation( &self, mut back_frame: Frame, @@ -281,6 +292,11 @@ impl Almanac { /// /// This function calls `occultation` where the back object is the Sun in the J2000 frame, and the front object /// is the provided eclipsing frame. + /// + /// :type eclipsing_frame: Frame + /// :type observer: Orbit + /// :type ab_corr: Aberration, optional + /// :rtype: Occultation pub fn solar_eclipsing( &self, eclipsing_frame: Frame, @@ -290,7 +306,8 @@ impl Almanac { self.occultation(SUN_J2000, eclipsing_frame, observer, ab_corr) } } -// Compute the area of the circular segment of radius r and chord length d + +/// Compute the area of the circular segment of radius r and chord length d fn circ_seg_area(r: f64, d: f64) -> f64 { r.powi(2) * (d / r).acos() - d * (r.powi(2) - d.powi(2)).sqrt() } diff --git a/anise/src/almanac/metaload/metaalmanac.rs b/anise/src/almanac/metaload/metaalmanac.rs index 95cea437..f826d69e 100644 --- a/anise/src/almanac/metaload/metaalmanac.rs +++ b/anise/src/almanac/metaload/metaalmanac.rs @@ -34,6 +34,9 @@ use super::{Almanac, MetaAlmanacError, MetaFile}; /// If the URI is a remote path, the MetaAlmanac will first check if the file exists locally. If it exists, it will check that the CRC32 checksum of this file matches that of the specs. /// If it does not match, the file will be downloaded again. If no CRC32 is provided but the file exists, then the MetaAlmanac will fetch the remote file and overwrite the existing file. /// The downloaded path will be stored in the "AppData" folder. +/// +/// :type maybe_path: str, optional +/// :rtype: MetaAlmanac #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[cfg_attr(feature = "python", pyclass)] #[cfg_attr(feature = "python", pyo3(module = "anise"))] @@ -92,7 +95,6 @@ impl MetaAlmanac { /// /// Note that the `earth_latest_high_prec.bpc` file is regularly updated daily (or so). As such, /// if queried at some future time, the Earth rotation parameters may have changed between two queries. - /// #[cfg(not(feature = "python"))] pub fn latest() -> AlmanacResult { Self::default().process(true) @@ -118,6 +120,8 @@ impl FromStr for MetaAlmanac { #[allow(deprecated_in_future)] impl MetaAlmanac { /// Dumps the configured Meta Almanac into a Dhall string. + /// + /// :rtype: str pub fn dumps(&self) -> Result { // Define the Dhall type let dhall_type: SimpleType = @@ -148,6 +152,9 @@ impl MetaAlmanac { } /// Loads the provided string as a Dhall configuration to build a MetaAlmanac + /// + /// :type s: str + /// :rtype: MetaAlmanac #[classmethod] fn loads(_cls: &Bound<'_, PyType>, s: String) -> Result { Self::from_str(&s) @@ -167,6 +174,10 @@ impl MetaAlmanac { /// Note that the `earth_latest_high_prec.bpc` file is regularly updated daily (or so). As such, /// if queried at some future time, the Earth rotation parameters may have changed between two queries. /// + /// Set `autodelete` to true to delete lock file if a dead lock is detected after 10 seconds. + /// + /// :type autodelete: bool, optional + /// :rtype: MetaAlmanac #[classmethod] fn latest( _cls: &Bound<'_, PyType>, @@ -185,6 +196,9 @@ impl MetaAlmanac { /// where multiple processes download the data at the same time. Set `autodelete` to true to delete /// this lock file if a dead lock is detected after 10 seconds. Set this flag to false if you have /// more than ten processes which may attempt to download files in parallel. + /// + /// :type autodelete: bool, optional + /// :rtype: Almanac pub fn process(&mut self, py: Python, autodelete: Option) -> AlmanacResult { py.allow_threads(|| self._process(autodelete.unwrap_or(true))) } diff --git a/anise/src/almanac/metaload/metafile.rs b/anise/src/almanac/metaload/metafile.rs index 45c12915..90681200 100644 --- a/anise/src/almanac/metaload/metafile.rs +++ b/anise/src/almanac/metaload/metafile.rs @@ -38,6 +38,10 @@ use super::MetaAlmanacError; /// The data is stored in the user's local temp directory (i.e. `~/.local/share/nyx-space/anise/` on Linux and `AppData/Local/nyx-space/anise/` on Windows). /// Prior to loading a remote resource, if the local resource exists, its CRC32 will be computed: if it matches the CRC32 of this instance of MetaFile, /// then the file will not be downloaded a second time. +/// +/// :type uri: str +/// :type crc32: int, optional +/// :rtype: MetaFile #[cfg_attr(feature = "python", pyclass)] #[cfg_attr(feature = "python", pyo3(module = "anise"))] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, StaticType)] @@ -289,6 +293,9 @@ impl MetaFile { /// Processes this MetaFile by downloading it if it's a URL. /// /// This function modified `self` and changes the URI to be the path to the downloaded file. + /// + /// :type autodelete: bool, optional + /// :rtype: None pub fn process( &mut self, py: Python, diff --git a/anise/src/almanac/metaload/mod.rs b/anise/src/almanac/metaload/mod.rs index 21d1f944..3b4ff15d 100644 --- a/anise/src/almanac/metaload/mod.rs +++ b/anise/src/almanac/metaload/mod.rs @@ -53,7 +53,6 @@ pub enum MetaAlmanacError { PersistentLock { desired: String }, } -#[cfg_attr(feature = "python", pymethods)] impl Almanac { /// Load from the provided MetaFile. fn _load_from_metafile(&self, mut metafile: MetaFile, autodelete: bool) -> AlmanacResult { @@ -63,8 +62,12 @@ impl Almanac { })?; self.load(&metafile.uri) } +} +#[cfg_attr(feature = "python", pymethods)] +impl Almanac { /// Load from the provided MetaFile, downloading it if necessary. + /// Set autodelete to true to automatically delete lock files. Lock files are important in multi-threaded loads. #[cfg(not(feature = "python"))] pub fn load_from_metafile(&self, metafile: MetaFile, autodelete: bool) -> AlmanacResult { self._load_from_metafile(metafile, autodelete) @@ -72,6 +75,12 @@ impl Almanac { #[cfg(feature = "python")] /// Load from the provided MetaFile, downloading it if necessary. + /// Set autodelete to true to automatically delete lock files. Lock files are important in multi-threaded loads. + /// + /// + /// :type metafile: Metafile + /// :type autodelete: bool + /// :rtype: Almanac pub fn load_from_metafile( &mut self, py: Python, diff --git a/anise/src/almanac/mod.rs b/anise/src/almanac/mod.rs index 9814bf9c..7bf65d5d 100644 --- a/anise/src/almanac/mod.rs +++ b/anise/src/almanac/mod.rs @@ -55,10 +55,10 @@ mod embed; #[cfg(feature = "python")] use pyo3::prelude::*; -/// An Almanac contains all of the loaded SPICE and ANISE data. +/// An Almanac contains all of the loaded SPICE and ANISE data. It is the context for all computations. /// -/// # Limitations -/// The stack space required depends on the maximum number of each type that can be loaded. +/// :type path: str +/// :rtype: Almanac #[derive(Clone, Default)] #[cfg_attr(feature = "python", pyclass)] #[cfg_attr(feature = "python", pyo3(module = "anise"))] @@ -201,6 +201,9 @@ impl Almanac { #[cfg_attr(feature = "python", pymethods)] impl Almanac { /// Generic function that tries to load the provided path guessing to the file type. + /// + /// :type path: str + /// :rtype: Almanac pub fn load(&self, path: &str) -> AlmanacResult { // Load the data onto the heap let bytes = file2heap!(path).context(LoadingSnafu { @@ -237,6 +240,13 @@ impl Almanac { /// Pretty prints the description of this Almanac, showing everything by default. Default time scale is TDB. /// If any parameter is set to true, then nothing other than that will be printed. + /// + /// :type spk: bool, optional + /// :type bpc: bool, optional + /// :type planetary: bool, optional + /// :type time_scale: TimeScale, optional + /// :type round_time: bool, optional + /// :rtype: None pub fn describe( &self, spk: Option, diff --git a/anise/src/almanac/python.rs b/anise/src/almanac/python.rs index e1b0f98e..93876c3b 100644 --- a/anise/src/almanac/python.rs +++ b/anise/src/almanac/python.rs @@ -18,6 +18,9 @@ use snafu::prelude::*; #[pymethods] impl Almanac { + /// Returns the frame information (gravitational param, shape) as defined in this Almanac from an empty frame + /// :type uid: Frame + /// :rtype: Frame pub fn frame_info(&self, uid: Frame) -> Result { Ok(self .planetary_data diff --git a/anise/src/almanac/solar.rs b/anise/src/almanac/solar.rs index a56b5356..5f87c928 100644 --- a/anise/src/almanac/solar.rs +++ b/anise/src/almanac/solar.rs @@ -55,6 +55,11 @@ impl Almanac { /// 1. Compute the position of the Sun as seen from the observer /// 2. Compute the position of the target as seen from the observer /// 3. Return the arccosine of the dot product of the norms of these vectors. + /// + /// :type target_id: int + /// :type observer_id: int + /// :type epoch: Epoch + /// :rtype: float pub fn sun_angle_deg( &self, target_id: NaifId, @@ -77,6 +82,11 @@ impl Almanac { } /// Convenience function that calls `sun_angle_deg` with the provided frames instead of the ephemeris ID. + /// + /// :type target: Frame + /// :type observer: Frame + /// :type epoch: Epoch + /// :rtype: float pub fn sun_angle_deg_from_frame( &self, target: Frame, diff --git a/anise/src/almanac/spk.rs b/anise/src/almanac/spk.rs index 075250dd..97b34402 100644 --- a/anise/src/almanac/spk.rs +++ b/anise/src/almanac/spk.rs @@ -196,6 +196,9 @@ impl Almanac { /// /// # Warning /// This function performs a memory allocation. + /// + /// :type id: int + /// :rtype: typing.List pub fn spk_summaries(&self, id: NaifId) -> Result, EphemerisError> { let mut summaries = vec![]; for maybe_spk in self.spk_data.iter().take(self.num_loaded_spk()).rev() { @@ -222,6 +225,9 @@ impl Almanac { } /// Returns the applicable domain of the request id, i.e. start and end epoch that the provided id has loaded data. + /// + /// :type id: int + /// :rtype: typing.Tuple pub fn spk_domain(&self, id: NaifId) -> Result<(Epoch, Epoch), EphemerisError> { let summaries = self.spk_summaries(id)?; @@ -245,6 +251,8 @@ impl Almanac { /// /// # Warning /// This function performs a memory allocation. + /// + /// :rtype: typing.Dict pub fn spk_domains(&self) -> Result, EphemerisError> { ensure!(self.num_loaded_spk() > 0, NoEphemerisLoadedSnafu); diff --git a/anise/src/almanac/transform.rs b/anise/src/almanac/transform.rs index 973db0ba..80564b52 100644 --- a/anise/src/almanac/transform.rs +++ b/anise/src/almanac/transform.rs @@ -39,6 +39,12 @@ impl Almanac { /// /// # Note /// The units will be those of the underlying ephemeris data (typically km and km/s) + /// + /// :type target_frame: Orbit + /// :type observer_frame: Frame + /// :type epoch: Epoch + /// :type ab_corr: Aberration, optional + /// :rtype: Orbit pub fn transform( &self, target_frame: Frame, @@ -69,6 +75,11 @@ impl Almanac { /// Translates a state with its origin (`to_frame`) and given its units (distance_unit, time_unit), returns that state with respect to the requested frame /// /// **WARNING:** This function only performs the translation and no rotation _whatsoever_. Use the `transform_state_to` function instead to include rotations. + /// + /// :type state: Orbit + /// :type observer_frame: Frame + /// :type ab_corr: Aberration, optional + /// :rtype: Orbit #[allow(clippy::too_many_arguments)] pub fn transform_to( &self, @@ -104,6 +115,12 @@ impl Almanac { /// /// # Note /// The units will be those of the underlying ephemeris data (typically km and km/s) + /// + /// :type object: int + /// :type observer: Frame + /// :type epoch: Epoch + /// :type ab_corr: Aberration, optional + /// :rtype: Orbit pub fn state_of( &self, object: NaifId, @@ -116,6 +133,12 @@ impl Almanac { /// Alias fo SPICE's `spkezr` where the inputs must be the NAIF IDs of the objects and frames with the caveat that the aberration is moved to the last positional argument. /// + /// :type target: int + /// :type epoch: Epoch + /// :type frame: int + /// :type observer: int + /// :type ab_corr: Aberration, optional + /// :rtype: Orbit pub fn spk_ezr( &self, target: NaifId, diff --git a/anise/src/astro/aberration.rs b/anise/src/astro/aberration.rs index 096e0dfc..014f153d 100644 --- a/anise/src/astro/aberration.rs +++ b/anise/src/astro/aberration.rs @@ -40,8 +40,7 @@ use crate::errors::PhysicsError; /// /// The validation test `validate_jplde_de440s_aberration_lt` checks 101,000 pairs of ephemeris computations and shows that the unconverged Light Time computation matches the SPICE computations almost all the time. /// More specifically, the 99th percentile of error is less than 5 meters, the 75th percentile is less than one meter, and the median error is less than 2 millimeters. -/// -/// # Python docstring +/// /// :type name: str /// :rtype: Aberration #[derive(Copy, Clone, Default, PartialEq, Eq)] diff --git a/anise/src/ephemerides/translate_to_parent.rs b/anise/src/ephemerides/translate_to_parent.rs index 20c529f4..6f009cbc 100644 --- a/anise/src/ephemerides/translate_to_parent.rs +++ b/anise/src/ephemerides/translate_to_parent.rs @@ -112,6 +112,10 @@ impl Almanac { #[cfg_attr(feature = "python", pymethods)] impl Almanac { /// Performs the GEOMETRIC translation to the parent. Use translate_from_to for aberration. + /// + /// :type source: Frame + /// :type epoch: Epoch + /// :rtype: Orbit pub fn translate_to_parent( &self, source: Frame, diff --git a/anise/src/ephemerides/translations.rs b/anise/src/ephemerides/translations.rs index b539f893..f24ceffc 100644 --- a/anise/src/ephemerides/translations.rs +++ b/anise/src/ephemerides/translations.rs @@ -46,6 +46,12 @@ impl Almanac { /// /// # Note /// This function performs a recursion of no more than twice the [MAX_TREE_DEPTH]. + /// + /// :type target_frame: Orbit + /// :type observer_frame: Frame + /// :type epoch: Epoch + /// :type ab_corr: Aberration, optional + /// :rtype: Orbit pub fn translate( &self, target_frame: Frame, @@ -170,6 +176,11 @@ impl Almanac { } /// Returns the geometric position vector, velocity vector, and acceleration vector needed to translate the `from_frame` to the `to_frame`, where the distance is in km, the velocity in km/s, and the acceleration in km/s^2. + /// + /// :type target_frame: Orbit + /// :type observer_frame: Frame + /// :type epoch: Epoch + /// :rtype: Orbit pub fn translate_geometric( &self, target_frame: Frame, @@ -182,6 +193,11 @@ impl Almanac { /// Translates the provided Cartesian state into the requested observer frame /// /// **WARNING:** This function only performs the translation and no rotation _whatsoever_. Use the `transform_to` function instead to include rotations. + /// + /// :type state: Orbit + /// :type observer_frame: Frame + /// :type ab_corr: Aberration, optional + /// :rtype: Orbit #[allow(clippy::too_many_arguments)] pub fn translate_to( &self, From 9c969449c7384d573bc92a04711313576b14c66b Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Fri, 1 Nov 2024 20:24:52 -0600 Subject: [PATCH 3/6] Continuing py type hints --- anise-py/README.md | 15 ++- anise-py/anise.astro.constants.pyi | 75 +++++++++++ anise-py/anise.astro.pyi | 0 anise-py/anise.pyi | 48 +++++-- anise-py/anise.time.pyi | 0 anise-py/anise.utils.pyi | 9 ++ anise-py/generate_stubs.py | 28 ++++- anise-py/src/astro.rs | 2 + anise-py/src/utils.rs | 14 ++- anise/src/astro/mod.rs | 117 +++++++++++++++++- anise/src/astro/occultation.rs | 73 ++++++++++- anise/src/frames/frame.rs | 47 ++++++- anise/src/math/cartesian_py.rs | 3 +- .../src/structure/planetocentric/ellipsoid.rs | 17 +++ 14 files changed, 422 insertions(+), 26 deletions(-) create mode 100644 anise-py/anise.astro.constants.pyi create mode 100644 anise-py/anise.astro.pyi create mode 100644 anise-py/anise.time.pyi create mode 100644 anise-py/anise.utils.pyi diff --git a/anise-py/README.md b/anise-py/README.md index a4055894..ffa1da86 100644 --- a/anise-py/README.md +++ b/anise-py/README.md @@ -123,7 +123,7 @@ if __name__ == "__main__": ``` -## Getting started as a developer +## Development 1. Install `maturin`, e.g. via `pipx` as `pipx install maturin` 1. Create a virtual environment: `cd anise/anise-py && python3 -m venv .venv` @@ -137,3 +137,16 @@ To run the development version of ANISE in a Jupyter Notebook, install ipykernel 1. Now, build the local kernel: `python -m ipykernel install --user --name=.venv` 1. Then, start jupyter notebook: `jupyter notebook` 1. Open the notebook, click on the top right and make sure to choose the environment you created just a few steps above. + +### Generating the pyi type hints + +Type hints are extremely useful for Python users. Building them is a bit of manual work. + +1. `maturin develop` to build the latest library +1. `python generate_stubs.py anise anise.pyi` builds the top level type hints +1. Repeat for all submodules: `utils`, `time`, `astro`, `astro.constants` writing to a new file each time: + 1. `python generate_stubs.py anise.astro anise.astro.pyi` + 1. `python generate_stubs.py anise.time anise.time.pyi` + 1. `python generate_stubs.py anise.astro.constants anise.astro.constants.pyi` + 1. `python generate_stubs.py anise.utils anise.utils.pyi` +1. Final, concat all of these new files back to `anise.pyi` since that's the only one used by `maturin`. \ No newline at end of file diff --git a/anise-py/anise.astro.constants.pyi b/anise-py/anise.astro.constants.pyi new file mode 100644 index 00000000..4b920c26 --- /dev/null +++ b/anise-py/anise.astro.constants.pyi @@ -0,0 +1,75 @@ +import typing + +@typing.final +class CelestialObjects: + EARTH: int = ... + EARTH_MOON_BARYCENTER: int = ... + JUPITER: int = ... + JUPITER_BARYCENTER: int = ... + MARS: int = ... + MARS_BARYCENTER: int = ... + MERCURY: int = ... + MOON: int = ... + NEPTUNE: int = ... + NEPTUNE_BARYCENTER: int = ... + PLUTO_BARYCENTER: int = ... + SATURN: int = ... + SATURN_BARYCENTER: int = ... + SOLAR_SYSTEM_BARYCENTER: int = ... + SUN: int = ... + URANUS: int = ... + URANUS_BARYCENTER: int = ... + VENUS: int = ... + +@typing.final +class Frames: + EARTH_ECLIPJ2000: Frame = ... + EARTH_ITRF93: Frame = ... + EARTH_J2000: Frame = ... + EARTH_MOON_BARYCENTER_J2000: Frame = ... + EME2000: Frame = ... + IAU_EARTH_FRAME: Frame = ... + IAU_JUPITER_FRAME: Frame = ... + IAU_MARS_FRAME: Frame = ... + IAU_MERCURY_FRAME: Frame = ... + IAU_MOON_FRAME: Frame = ... + IAU_NEPTUNE_FRAME: Frame = ... + IAU_SATURN_FRAME: Frame = ... + IAU_URANUS_FRAME: Frame = ... + IAU_VENUS_FRAME: Frame = ... + JUPITER_BARYCENTER_J2000: Frame = ... + MARS_BARYCENTER_J2000: Frame = ... + MERCURY_J2000: Frame = ... + MOON_J2000: Frame = ... + MOON_ME_FRAME: Frame = ... + MOON_PA_FRAME: Frame = ... + NEPTUNE_BARYCENTER_J2000: Frame = ... + PLUTO_BARYCENTER_J2000: Frame = ... + SATURN_BARYCENTER_J2000: Frame = ... + SSB_J2000: Frame = ... + SUN_J2000: Frame = ... + URANUS_BARYCENTER_J2000: Frame = ... + VENUS_J2000: Frame = ... + +@typing.final +class Orientations: + ECLIPJ2000: int = ... + IAU_EARTH: int = ... + IAU_JUPITER: int = ... + IAU_MARS: int = ... + IAU_MERCURY: int = ... + IAU_MOON: int = ... + IAU_NEPTUNE: int = ... + IAU_SATURN: int = ... + IAU_URANUS: int = ... + IAU_VENUS: int = ... + ITRF93: int = ... + J2000: int = ... + MOON_ME: int = ... + MOON_PA: int = ... + +@typing.final +class UsualConstants: + MEAN_EARTH_ANGULAR_VELOCITY_DEG_S: float = ... + MEAN_MOON_ANGULAR_VELOCITY_DEG_S: float = ... + SPEED_OF_LIGHT_KM_S: float = ... \ No newline at end of file diff --git a/anise-py/anise.astro.pyi b/anise-py/anise.astro.pyi new file mode 100644 index 00000000..e69de29b diff --git a/anise-py/anise.pyi b/anise-py/anise.pyi index 2dd10796..a040b59b 100644 --- a/anise-py/anise.pyi +++ b/anise-py/anise.pyi @@ -18,9 +18,7 @@ For more details, bool: """Return self==value.""" @@ -404,4 +400,42 @@ This function modified `self` and changes the URI to be the path to the download """Return repr(self).""" def __str__(self) -> str: - """Return str(self).""" \ No newline at end of file + """Return str(self).""" + +@typing.final +class astro: + AzElRange: type = ... + Ellipsoid: type = ... + Frame: type = ... + Orbit: type = ... + __all__: list = ... + __name__: str = ... + astro.constants: module = ... + +@typing.final +class time: + Duration: type = ... + Epoch: type = ... + LatestLeapSeconds: type = ... + LeapSecondsFile: type = ... + TimeScale: type = ... + TimeSeries: type = ... + Unit: type = ... + Ut1Provider: type = ... + __all__: list = ... + __name__: str = ... + +@typing.final +class utils: + + @staticmethod + def convert_fk(fk_file_path: str, anise_output_path: str, show_comments: bool=None, overwrite: bool=None) -> None: + """Converts a KPL/FK file, that defines frame constants like fixed rotations, and frame name to ID mappings into the EulerParameterDataSet equivalent ANISE file. +KPL/FK files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE.""" + + @staticmethod + def convert_tpc(pck_file_path: str, gm_file_path: str, anise_output_path: str, overwrite: bool=None) -> None: + """Converts two KPL/TPC files, one defining the planetary constants as text, and the other defining the gravity parameters, into the PlanetaryDataSet equivalent ANISE file. +KPL/TPC files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE.""" + __all__: list = ... + __name__: str = ... \ No newline at end of file diff --git a/anise-py/anise.time.pyi b/anise-py/anise.time.pyi new file mode 100644 index 00000000..e69de29b diff --git a/anise-py/anise.utils.pyi b/anise-py/anise.utils.pyi new file mode 100644 index 00000000..3f218614 --- /dev/null +++ b/anise-py/anise.utils.pyi @@ -0,0 +1,9 @@ +import typing + +def convert_fk(fk_file_path: str, anise_output_path: str, show_comments: bool=None, overwrite: bool=None) -> None: + """Converts a KPL/FK file, that defines frame constants like fixed rotations, and frame name to ID mappings into the EulerParameterDataSet equivalent ANISE file. +KPL/FK files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE.""" + +def convert_tpc(pck_file_path: str, gm_file_path: str, anise_output_path: str, overwrite: bool=None) -> None: + """Converts two KPL/TPC files, one defining the planetary constants as text, and the other defining the gravity parameters, into the PlanetaryDataSet equivalent ANISE file. +KPL/TPC files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE.""" \ No newline at end of file diff --git a/anise-py/generate_stubs.py b/anise-py/generate_stubs.py index 8da22cd0..b456bc67 100644 --- a/anise-py/generate_stubs.py +++ b/anise-py/generate_stubs.py @@ -72,8 +72,6 @@ def module_stubs(module: Any) -> ast.Module: element_path = [module.__name__, member_name] if member_name.startswith("__"): pass - elif member_name.startswith("DoraStatus"): - pass elif inspect.isclass(member_value): classes.append( class_stubs(member_name, member_value, element_path, types_to_import) @@ -242,7 +240,7 @@ def function_stub( decorator_list = [] if in_class and hasattr(fn_def, "__self__"): decorator_list.append(ast.Name("staticmethod")) - + print(f"Documenting {fn_name}") return ast.FunctionDef( @@ -271,7 +269,7 @@ def arguments_stub( real_parameters: Mapping[str, inspect.Parameter] = inspect.signature( callable_def ).parameters - + if callable_name == "__init__": real_parameters = { "self": inspect.Parameter("self", inspect.Parameter.POSITIONAL_ONLY), @@ -290,7 +288,16 @@ def arguments_stub( for name, t in zip(param_names, builtin[0]): parsed_param_types[name] = t - elif callable_name in ["__add__", "__sub__", "__div__", "__mul__", "__radd__", "__rsub__", "__rdiv__", "__rmul__"]: + elif callable_name in [ + "__add__", + "__sub__", + "__div__", + "__mul__", + "__radd__", + "__rsub__", + "__rdiv__", + "__rmul__", + ]: return ast.arguments(posonlyargs=[], args=[], defaults=[], kwonlyargs=[]) # Types from comment @@ -375,7 +382,16 @@ def returns_stub( # Don't document errors return - if callable_name in ["__add__", "__sub__", "__div__", "__mul__", "__radd__", "__rsub__", "__rdiv__", "__rmul__"]: + if callable_name in [ + "__add__", + "__sub__", + "__div__", + "__mul__", + "__radd__", + "__rsub__", + "__rdiv__", + "__rmul__", + ]: return m = re.findall(r"^ *:rtype: *([^\n]*) *$", doc, re.MULTILINE) if len(m) == 0: diff --git a/anise-py/src/astro.rs b/anise-py/src/astro.rs index 10ee5479..f3b5d3c4 100644 --- a/anise-py/src/astro.rs +++ b/anise-py/src/astro.rs @@ -9,6 +9,7 @@ */ use anise::astro::AzElRange; +use anise::astro::Occultation; use anise::structure::planetocentric::ellipsoid::Ellipsoid; use pyo3::prelude::*; use pyo3::py_run; @@ -24,6 +25,7 @@ pub(crate) fn register_astro(parent_module: &Bound<'_, PyModule>) -> PyResult<() sm.add_class::()?; sm.add_class::()?; sm.add_class::()?; + sm.add_class::()?; register_constants(&sm)?; diff --git a/anise-py/src/utils.rs b/anise-py/src/utils.rs index bd38ff71..34446991 100644 --- a/anise-py/src/utils.rs +++ b/anise-py/src/utils.rs @@ -12,12 +12,10 @@ use std::path::PathBuf; use anise::naif::kpl::parser::{convert_fk as convert_fk_rs, convert_tpc as convert_tpc_rs}; use anise::structure::dataset::DataSetError; -use anise::structure::planetocentric::ellipsoid::Ellipsoid; use pyo3::{prelude::*, py_run}; pub(crate) fn register_utils(parent_module: &Bound<'_, PyModule>) -> PyResult<()> { let sm = PyModule::new_bound(parent_module.py(), "utils")?; - sm.add_class::()?; sm.add_function(wrap_pyfunction!(convert_fk, &sm)?)?; sm.add_function(wrap_pyfunction!(convert_tpc, &sm)?)?; @@ -30,6 +28,12 @@ pub(crate) fn register_utils(parent_module: &Bound<'_, PyModule>) -> PyResult<() /// Converts a KPL/FK file, that defines frame constants like fixed rotations, and frame name to ID mappings into the EulerParameterDataSet equivalent ANISE file. /// KPL/FK files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE. +/// +/// :type fk_file_path: str +/// :type anise_output_path: str +/// :type show_comments: bool, optional +/// :type overwrite: bool, optional +/// :rtype: None #[pyfunction] fn convert_fk( fk_file_path: String, @@ -49,6 +53,12 @@ fn convert_fk( /// Converts two KPL/TPC files, one defining the planetary constants as text, and the other defining the gravity parameters, into the PlanetaryDataSet equivalent ANISE file. /// KPL/TPC files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE. +/// +/// :type pck_file_path: str +/// :type gm_file_path: str +/// :type anise_output_path: str +/// :type overwrite: bool, optional +/// :rtype: None #[pyfunction] fn convert_tpc( pck_file_path: String, diff --git a/anise/src/astro/mod.rs b/anise/src/astro/mod.rs index e596936c..ae0f65da 100644 --- a/anise/src/astro/mod.rs +++ b/anise/src/astro/mod.rs @@ -36,9 +36,16 @@ pub mod orbit_geodetic; pub type PhysicsResult = Result; /// A structure that stores the result of Azimuth, Elevation, Range, Range rate calculation. +/// +/// :type epoch: Epoch +/// :type azimuth_deg: float +/// :type elevation_deg: float +/// :type range_km: float +/// :type range_rate_km_s: float +/// :type obstructed_by: Frame, optional +/// :rtype: AzElRange #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "python", pyclass)] -#[cfg_attr(feature = "python", pyo3(get_all, set_all))] #[cfg_attr(feature = "python", pyo3(module = "anise.astro"))] pub struct AzElRange { pub epoch: Epoch, @@ -53,17 +60,24 @@ pub struct AzElRange { #[cfg_attr(feature = "python", pymethods)] impl AzElRange { /// Returns false if the range is less than one millimeter, or any of the angles are NaN. + /// + /// :rtype: bool pub fn is_valid(&self) -> bool { self.azimuth_deg.is_finite() && self.elevation_deg.is_finite() && self.range_km > 1e-6 } /// Returns whether there is an obstruction. + /// + /// :rtype: bool pub const fn is_obstructed(&self) -> bool { self.obstructed_by.is_some() } +} +#[cfg_attr(feature = "python", pymethods)] +#[cfg(feature = "python")] +impl AzElRange { /// Initializes a new AzElRange instance - #[cfg(feature = "python")] #[new] pub fn py_new( epoch: Epoch, @@ -87,17 +101,96 @@ impl AzElRange { } } - #[cfg(feature = "python")] + /// :rtype: Epoch + #[getter] + fn get_epoch(&self) -> PyResult { + Ok(self.epoch) + } + /// :type epoch: Epoch + #[setter] + fn set_epoch(&mut self, epoch: Epoch) -> PyResult<()> { + self.epoch = epoch; + Ok(()) + } + + /// :rtype: float + #[getter] + fn get_azimuth_deg(&self) -> PyResult { + Ok(self.azimuth_deg) + } + /// :type azimuth_deg: f64 + #[setter] + fn set_azimuth_deg(&mut self, azimuth_deg: f64) -> PyResult<()> { + self.azimuth_deg = azimuth_deg; + Ok(()) + } + + /// :rtype: float + #[getter] + fn get_elevation_deg(&self) -> PyResult { + Ok(self.elevation_deg) + } + /// :type elevation_deg: f64 + #[setter] + fn set_elevation_deg(&mut self, elevation_deg: f64) -> PyResult<()> { + self.elevation_deg = elevation_deg; + Ok(()) + } + + /// :rtype: float + #[getter] + fn get_range_km(&self) -> PyResult { + Ok(self.range_km) + } + /// :type range_km: f64 + #[setter] + fn set_range_km(&mut self, range_km: f64) -> PyResult<()> { + use crate::constants::SPEED_OF_LIGHT_KM_S; + use hifitime::TimeUnits; + + self.range_km = range_km; + self.light_time = (range_km / SPEED_OF_LIGHT_KM_S).seconds(); + Ok(()) + } + + /// :rtype: float + #[getter] + fn get_range_rate_km_s(&self) -> PyResult { + Ok(self.range_rate_km_s) + } + /// :type range_rate_km_s: f64 + #[setter] + fn set_range_rate_km_s(&mut self, range_rate_km_s: f64) -> PyResult<()> { + self.range_rate_km_s = range_rate_km_s; + Ok(()) + } + + /// :rtype: Frame + #[getter] + fn get_obstructed_by(&self) -> PyResult> { + Ok(self.obstructed_by) + } + /// :type obstructed_by: Frame + #[setter] + fn set_obstructed_by(&mut self, obstructed_by: Option) -> PyResult<()> { + self.obstructed_by = obstructed_by; + Ok(()) + } + + /// :rtype: Duration + #[getter] + fn get_light_time(&self) -> PyResult { + Ok(self.light_time) + } + fn __str__(&self) -> String { format!("{self}") } - #[cfg(feature = "python")] fn __repr__(&self) -> String { format!("{self} (@{self:p})") } - #[cfg(feature = "python")] fn __richcmp__(&self, other: &Self, op: CompareOp) -> Result { match op { CompareOp::Eq => Ok(self == other), @@ -107,6 +200,20 @@ impl AzElRange { ))), } } + + /// Allows for pickling the object + /// + /// :rtype: typing.Tuple + fn __getnewargs__(&self) -> Result<(Epoch, f64, f64, f64, f64, Option), PyErr> { + Ok(( + self.epoch, + self.azimuth_deg, + self.elevation_deg, + self.range_km, + self.range_rate_km_s, + self.obstructed_by, + )) + } } impl Display for AzElRange { diff --git a/anise/src/astro/occultation.rs b/anise/src/astro/occultation.rs index 720719a7..17ca9d9c 100644 --- a/anise/src/astro/occultation.rs +++ b/anise/src/astro/occultation.rs @@ -21,8 +21,7 @@ use pyo3::prelude::*; /// Refer to the [MathSpec](https://nyxspace.com/nyxspace/MathSpec/celestial/eclipse/) for modeling details. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "python", pyclass)] -#[cfg_attr(feature = "python", pyo3(module = "anise"))] -#[cfg_attr(feature = "python", pyo3(get_all, set_all))] +#[cfg_attr(feature = "python", pyo3(module = "anise.astro"))] pub struct Occultation { pub epoch: Epoch, pub percentage: f64, @@ -33,31 +32,101 @@ pub struct Occultation { #[cfg_attr(feature = "python", pymethods)] impl Occultation { /// Returns the percentage as a factor between 0 and 1 + /// + /// :rtype: float pub fn factor(&self) -> f64 { self.percentage / 100.0 } /// Returns true if the back object is the Sun, false otherwise + /// + /// :rtype: bool pub const fn is_eclipse_computation(&self) -> bool { self.back_frame.ephem_origin_id_match(SUN) } /// Returns true if the occultation percentage is less than or equal 0.001% + /// + /// :rtype: bool pub fn is_visible(&self) -> bool { self.percentage < 1e-3 } /// Returns true if the occultation percentage is greater than or equal 99.999% + /// + /// :rtype: bool pub fn is_obstructed(&self) -> bool { self.percentage > 99.999 } /// Returns true if neither occulted nor visible (i.e. penumbra for solar eclipsing) + /// + /// :rtype: bool pub fn is_partial(&self) -> bool { !self.is_visible() && !self.is_obstructed() } } +#[cfg_attr(feature = "python", pymethods)] +#[cfg(feature = "python")] +impl Occultation { + /// :rtype: Epoch + #[getter] + fn get_epoch(&self) -> PyResult { + Ok(self.epoch) + } + /// :type epoch: Epoch + #[setter] + fn set_epoch(&mut self, epoch: Epoch) -> PyResult<()> { + self.epoch = epoch; + Ok(()) + } + + /// :rtype: float + #[getter] + fn get_percentage(&self) -> PyResult { + Ok(self.percentage) + } + /// :type epoch: Epoch + #[setter] + fn set_percentage(&mut self, percentage: f64) -> PyResult<()> { + self.percentage = percentage; + Ok(()) + } + + /// :rtype: Frame + #[getter] + fn get_back_frame(&self) -> PyResult { + Ok(self.back_frame) + } + /// :type back_frame: Frame + #[setter] + fn set_back_frame(&mut self, back_frame: Frame) -> PyResult<()> { + self.back_frame = back_frame; + Ok(()) + } + + /// :rtype: Frame + #[getter] + fn get_front_frame(&self) -> PyResult { + Ok(self.front_frame) + } + /// :type front_frame: Frame + #[setter] + fn set_front_frame(&mut self, front_frame: Frame) -> PyResult<()> { + self.front_frame = front_frame; + Ok(()) + } + + fn __str__(&self) -> String { + format!("{self}") + } + + fn __repr__(&self) -> String { + format!("{self} (@{self:p})") + } +} + impl fmt::Display for Occultation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_eclipse_computation() { diff --git a/anise/src/frames/frame.rs b/anise/src/frames/frame.rs index 87b1dd86..92a821eb 100644 --- a/anise/src/frames/frame.rs +++ b/anise/src/frames/frame.rs @@ -34,6 +34,12 @@ use pyo3::prelude::*; use pyo3::pyclass::CompareOp; /// A Frame uniquely defined by its ephemeris center and orientation. Refer to FrameDetail for frames combined with parameters. +/// +/// :type ephemeris_id: int +/// :type orientation_id: int +/// :type mu_km3_s2: float, optional +/// :type shape: Ellipsoid, optional +/// :rtype: Frame #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] #[cfg_attr(feature = "metaload", derive(StaticType))] #[cfg_attr(feature = "python", pyclass)] @@ -125,6 +131,8 @@ impl Frame { } /// Allows for pickling the object + /// + /// :rtype: typing.Tuple fn __getnewargs__(&self) -> Result<(NaifId, NaifId, Option, Option), PyErr> { Ok(( self.ephemeris_id, @@ -183,6 +191,9 @@ impl Frame { #[cfg_attr(feature = "python", pymethods)] impl Frame { /// Returns a copy of this Frame whose ephemeris ID is set to the provided ID + /// + /// :type new_ephem_id: int + /// :rtype: Frame pub const fn with_ephem(&self, new_ephem_id: NaifId) -> Self { let mut me = *self; me.ephemeris_id = new_ephem_id; @@ -190,6 +201,9 @@ impl Frame { } /// Returns a copy of this Frame whose orientation ID is set to the provided ID + /// + /// :type new_orient_id: int + /// :rtype: Frame pub const fn with_orient(&self, new_orient_id: NaifId) -> Self { let mut me = *self; me.orientation_id = new_orient_id; @@ -197,40 +211,60 @@ impl Frame { } /// Returns whether this is a celestial frame + /// + /// :rtype: bool pub const fn is_celestial(&self) -> bool { self.mu_km3_s2.is_some() } /// Returns whether this is a geodetic frame + /// + /// :rtype: bool pub const fn is_geodetic(&self) -> bool { self.mu_km3_s2.is_some() && self.shape.is_some() } /// Returns true if the ephemeris origin is equal to the provided ID + /// + /// :type other_id: int + /// :rtype: bool pub const fn ephem_origin_id_match(&self, other_id: NaifId) -> bool { self.ephemeris_id == other_id } /// Returns true if the orientation origin is equal to the provided ID + /// + /// :type other_id: int + /// :rtype: bool pub const fn orient_origin_id_match(&self, other_id: NaifId) -> bool { self.orientation_id == other_id } /// Returns true if the ephemeris origin is equal to the provided frame + /// + /// :type other: Frame + /// :rtype: bool pub const fn ephem_origin_match(&self, other: Self) -> bool { self.ephem_origin_id_match(other.ephemeris_id) } /// Returns true if the orientation origin is equal to the provided frame + /// + /// :type other: Frame + /// :rtype: bool pub const fn orient_origin_match(&self, other: Self) -> bool { self.orient_origin_id_match(other.orientation_id) } /// Removes the graviational parameter and the shape information from this frame. /// Use this to prevent astrodynamical computations. - pub(crate) fn strip(&mut self) { + /// + /// :rtype: None + pub fn strip(&mut self) { self.mu_km3_s2 = None; self.shape = None; } /// Returns the gravitational parameters of this frame, if defined + /// + /// :rtype: float pub fn mu_km3_s2(&self) -> PhysicsResult { self.mu_km3_s2.ok_or(PhysicsError::MissingFrameData { action: "retrieving gravitational parameter", @@ -240,6 +274,9 @@ impl Frame { } /// Returns a copy of this frame with the graviational parameter set to the new value. + /// + /// :type mu_km3_s2: float + /// :rtype: Frame pub fn with_mu_km3_s2(&self, mu_km3_s2: f64) -> Self { let mut me = *self; me.mu_km3_s2 = Some(mu_km3_s2); @@ -247,6 +284,8 @@ impl Frame { } /// Returns the mean equatorial radius in km, if defined + /// + /// :rtype: float pub fn mean_equatorial_radius_km(&self) -> PhysicsResult { Ok(self .shape @@ -259,6 +298,8 @@ impl Frame { } /// Returns the semi major radius of the tri-axial ellipoid shape of this frame, if defined + /// + /// :rtype: float pub fn semi_major_radius_km(&self) -> PhysicsResult { Ok(self .shape @@ -271,6 +312,8 @@ impl Frame { } /// Returns the flattening ratio (unitless) + /// + /// :rtype: float pub fn flattening(&self) -> PhysicsResult { Ok(self .shape @@ -283,6 +326,8 @@ impl Frame { } /// Returns the polar radius in km, if defined + /// + /// :rtype: float pub fn polar_radius_km(&self) -> PhysicsResult { Ok(self .shape diff --git a/anise/src/math/cartesian_py.rs b/anise/src/math/cartesian_py.rs index 7edb8a3e..6faaebea 100644 --- a/anise/src/math/cartesian_py.rs +++ b/anise/src/math/cartesian_py.rs @@ -133,7 +133,6 @@ impl CartesianState { format!("{self}") } - #[cfg(feature = "python")] fn __repr__(&self) -> String { format!("{self} (@{self:p})") } @@ -149,7 +148,7 @@ impl CartesianState { } #[allow(clippy::type_complexity)] - #[cfg(feature = "python")] + /// :rtype: typing.Tuple fn __getnewargs__(&self) -> Result<(f64, f64, f64, f64, f64, f64, Epoch, Frame), PyErr> { Ok(( self.radius_km[0], diff --git a/anise/src/structure/planetocentric/ellipsoid.rs b/anise/src/structure/planetocentric/ellipsoid.rs index 404dd36c..34a7bb47 100644 --- a/anise/src/structure/planetocentric/ellipsoid.rs +++ b/anise/src/structure/planetocentric/ellipsoid.rs @@ -32,6 +32,11 @@ use pyo3::pyclass::CompareOp; /// Example: Radii of the Earth. /// /// BODY399_RADII = ( 6378.1366 6378.1366 6356.7519 ) +/// +/// :type semi_major_equatorial_radius_km: float +/// :type polar_radius_km: float, optional +/// :type semi_minor_equatorial_radius_km: float, optional +/// :rtype: Ellipsoid #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)] #[cfg_attr(feature = "metaload", derive(StaticType))] #[cfg_attr(feature = "python", pyclass)] @@ -106,6 +111,8 @@ impl Ellipsoid { } /// Allows for pickling the object + /// + /// :rtype: typing.Tuple fn __getnewargs__(&self) -> Result<(f64, Option, Option), PyErr> { Ok(( self.semi_major_equatorial_radius_km, @@ -158,21 +165,31 @@ impl Ellipsoid { #[cfg_attr(feature = "python", pymethods)] impl Ellipsoid { /// Returns the mean equatorial radius in kilometers + /// + /// :rtype: float pub fn mean_equatorial_radius_km(&self) -> f64 { (self.semi_major_equatorial_radius_km + self.semi_minor_equatorial_radius_km) / 2.0 } + /// Returns true if the polar radius is equal to the semi minor radius. + /// + /// :rtype: bool pub fn is_sphere(&self) -> bool { self.is_spheroid() && (self.polar_radius_km - self.semi_minor_equatorial_radius_km).abs() < f64::EPSILON } + /// Returns true if the semi major and minor radii are equal + /// + /// :rtype: bool pub fn is_spheroid(&self) -> bool { (self.semi_major_equatorial_radius_km - self.semi_minor_equatorial_radius_km).abs() < f64::EPSILON } /// Returns the flattening ratio, computed from the mean equatorial radius and the polar radius + /// + /// :rtype: float pub fn flattening(&self) -> f64 { (self.mean_equatorial_radius_km() - self.polar_radius_km) / self.mean_equatorial_radius_km() } From 67b15ec03459e32d875a34b118bd1d4d26b47d52 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Fri, 1 Nov 2024 21:35:44 -0600 Subject: [PATCH 4/6] Add pyi astro --- anise-py/anise.astro.pyi | 615 ++++++++++++++++++++++++++++++ anise/src/astro/orbit.rs | 196 +++++++++- anise/src/astro/orbit_geodetic.rs | 132 +++++-- anise/src/math/cartesian.rs | 54 +++ anise/src/math/cartesian_py.rs | 54 ++- 5 files changed, 988 insertions(+), 63 deletions(-) diff --git a/anise-py/anise.astro.pyi b/anise-py/anise.astro.pyi index e69de29b..3aa20b7b 100644 --- a/anise-py/anise.astro.pyi +++ b/anise-py/anise.astro.pyi @@ -0,0 +1,615 @@ +import typing + +@typing.final +class AzElRange: + """A structure that stores the result of Azimuth, Elevation, Range, Range rate calculation.""" + azimuth_deg: float + elevation_deg: float + epoch: Epoch + light_time: Duration + obstructed_by: Frame + range_km: float + range_rate_km_s: float + + def __init__(self, epoch: Epoch, azimuth_deg: float, elevation_deg: float, range_km: float, range_rate_km_s: float, obstructed_by: Frame=None) -> AzElRange: + """A structure that stores the result of Azimuth, Elevation, Range, Range rate calculation.""" + + def is_obstructed(self) -> bool: + """Returns whether there is an obstruction.""" + + def is_valid(self) -> bool: + """Returns false if the range is less than one millimeter, or any of the angles are NaN.""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self) -> typing.Tuple: + """Allows for pickling the object""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + +@typing.final +class Ellipsoid: + """Only the tri-axial Ellipsoid shape model is currently supported by ANISE. +This is directly inspired from SPICE PCK. +> For each body, three radii are listed: The first number is +> the largest equatorial radius (the length of the semi-axis +> containing the prime meridian), the second number is the smaller +> equatorial radius, and the third is the polar radius. + +Example: Radii of the Earth. + +BODY399_RADII = ( 6378.1366 6378.1366 6356.7519 )""" + polar_radius_km: float + semi_major_equatorial_radius_km: float + semi_minor_equatorial_radius_km: float + + def __init__(self, semi_major_equatorial_radius_km: float, polar_radius_km: float=None, semi_minor_equatorial_radius_km: float=None) -> Ellipsoid: + """Only the tri-axial Ellipsoid shape model is currently supported by ANISE. +This is directly inspired from SPICE PCK. +> For each body, three radii are listed: The first number is +> the largest equatorial radius (the length of the semi-axis +> containing the prime meridian), the second number is the smaller +> equatorial radius, and the third is the polar radius. + +Example: Radii of the Earth. + +BODY399_RADII = ( 6378.1366 6378.1366 6356.7519 )""" + + def flattening(self) -> float: + """Returns the flattening ratio, computed from the mean equatorial radius and the polar radius""" + + def is_sphere(self) -> bool: + """Returns true if the polar radius is equal to the semi minor radius.""" + + def is_spheroid(self) -> bool: + """Returns true if the semi major and minor radii are equal""" + + def mean_equatorial_radius_km(self) -> float: + """Returns the mean equatorial radius in kilometers""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self) -> typing.Tuple: + """Allows for pickling the object""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + +@typing.final +class Frame: + """A Frame uniquely defined by its ephemeris center and orientation. Refer to FrameDetail for frames combined with parameters.""" + ephemeris_id: int + orientation_id: int + shape: Ellipsoid + + def __init__(self, ephemeris_id: int, orientation_id: int, mu_km3_s2: float=None, shape: Ellipsoid=None) -> Frame: + """A Frame uniquely defined by its ephemeris center and orientation. Refer to FrameDetail for frames combined with parameters.""" + + def ephem_origin_id_match(self, other_id: int) -> bool: + """Returns true if the ephemeris origin is equal to the provided ID""" + + def ephem_origin_match(self, other: Frame) -> bool: + """Returns true if the ephemeris origin is equal to the provided frame""" + + def flattening(self) -> float: + """Returns the flattening ratio (unitless)""" + + def is_celestial(self) -> bool: + """Returns whether this is a celestial frame""" + + def is_geodetic(self) -> bool: + """Returns whether this is a geodetic frame""" + + def mean_equatorial_radius_km(self) -> float: + """Returns the mean equatorial radius in km, if defined""" + + def mu_km3_s2(self) -> float: + """Returns the gravitational parameters of this frame, if defined""" + + def orient_origin_id_match(self, other_id: int) -> bool: + """Returns true if the orientation origin is equal to the provided ID""" + + def orient_origin_match(self, other: Frame) -> bool: + """Returns true if the orientation origin is equal to the provided frame""" + + def polar_radius_km(self) -> float: + """Returns the polar radius in km, if defined""" + + def semi_major_radius_km(self) -> float: + """Returns the semi major radius of the tri-axial ellipoid shape of this frame, if defined""" + + def strip(self) -> None: + """Removes the graviational parameter and the shape information from this frame. +Use this to prevent astrodynamical computations.""" + + def with_ephem(self, new_ephem_id: int) -> Frame: + """Returns a copy of this Frame whose ephemeris ID is set to the provided ID""" + + def with_mu_km3_s2(self, mu_km3_s2: float) -> Frame: + """Returns a copy of this frame with the graviational parameter set to the new value.""" + + def with_orient(self, new_orient_id: int) -> Frame: + """Returns a copy of this Frame whose orientation ID is set to the provided ID""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self) -> typing.Tuple: + """Allows for pickling the object""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + +@typing.final +class Occultation: + """Stores the result of an occultation computation with the occulation percentage +Refer to the [MathSpec](https://nyxspace.com/nyxspace/MathSpec/celestial/eclipse/) for modeling details.""" + back_frame: Frame + epoch: Epoch + front_frame: Frame + percentage: float + + def factor(self) -> float: + """Returns the percentage as a factor between 0 and 1""" + + def is_eclipse_computation(self) -> bool: + """Returns true if the back object is the Sun, false otherwise""" + + def is_obstructed(self) -> bool: + """Returns true if the occultation percentage is greater than or equal 99.999%""" + + def is_partial(self) -> bool: + """Returns true if neither occulted nor visible (i.e. penumbra for solar eclipsing)""" + + def is_visible(self) -> bool: + """Returns true if the occultation percentage is less than or equal 0.001%""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + +@typing.final +class Orbit: + """Defines a Cartesian state in a given frame at a given epoch in a given time scale. Radius data is expressed in kilometers. Velocity data is expressed in kilometers per second. +Regardless of the constructor used, this struct stores all the state information in Cartesian coordinates as these are always non singular. + +Unless noted otherwise, algorithms are from GMAT 2016a [StateConversionUtil.cpp](https://github.com/ChristopherRabotin/GMAT/blob/37201a6290e7f7b941bc98ee973a527a5857104b/src/base/util/StateConversionUtil.cpp).""" + epoch: Epoch + frame: Frame + vx_km_s: float + vy_km_s: float + vz_km: None + vz_km_s: float + x_km: float + y_km: float + z_km: float + + def __init__(self, x_km: float, y_km: float, z_km: float, vx_km_s: float, vy_km_s: float, vz_km_s: float, epoch: Epoch, frame: Frame) -> Orbit: + """Defines a Cartesian state in a given frame at a given epoch in a given time scale. Radius data is expressed in kilometers. Velocity data is expressed in kilometers per second. +Regardless of the constructor used, this struct stores all the state information in Cartesian coordinates as these are always non singular. + +Unless noted otherwise, algorithms are from GMAT 2016a [StateConversionUtil.cpp](https://github.com/ChristopherRabotin/GMAT/blob/37201a6290e7f7b941bc98ee973a527a5857104b/src/base/util/StateConversionUtil.cpp).""" + + def abs_difference(self, other: Orbit) -> typing.Tuple: + """Returns the absolute position and velocity differences in km and km/s between this orbit and another. +Raises an error if the frames do not match (epochs do not need to match).""" + + def abs_pos_diff_km(self, other: Orbit) -> float: + """Returns the absolute position difference in kilometer between this orbit and another. +Raises an error if the frames do not match (epochs do not need to match).""" + + def abs_vel_diff_km_s(self, other: Orbit) -> float: + """Returns the absolute velocity difference in kilometer per second between this orbit and another. +Raises an error if the frames do not match (epochs do not need to match).""" + + def add_aop_deg(self, delta_aop_deg: float) -> Orbit: + """Returns a copy of the state with a provided AOP added to the current one""" + + def add_apoapsis_periapsis_km(self, delta_ra_km: float, delta_rp_km: float) -> Orbit: + """Returns a copy of this state with the provided apoasis and periapsis added to the current values""" + + def add_ecc(self, delta_ecc: float) -> Orbit: + """Returns a copy of the state with a provided ECC added to the current one""" + + def add_inc_deg(self, delta_inc_deg: float) -> None: + """Returns a copy of the state with a provided INC added to the current one""" + + def add_raan_deg(self, delta_raan_deg: float) -> Orbit: + """Returns a copy of the state with a provided RAAN added to the current one""" + + def add_sma_km(self, delta_sma_km: float) -> Orbit: + """Returns a copy of the state with a provided SMA added to the current one""" + + def add_ta_deg(self, delta_ta_deg: float) -> Orbit: + """Returns a copy of the state with a provided TA added to the current one""" + + def aol_deg(self) -> float: + """Returns the argument of latitude in degrees + +NOTE: If the orbit is near circular, the AoL will be computed from the true longitude +instead of relying on the ill-defined true anomaly.""" + + def aop_deg(self) -> float: + """Returns the argument of periapsis in degrees""" + + def apoapsis_altitude_km(self) -> float: + """Returns the altitude of apoapsis (or apogee around Earth), in kilometers.""" + + def apoapsis_km(self) -> float: + """Returns the radius of apoapsis (or apogee around Earth), in kilometers.""" + + def at_epoch(self, new_epoch: Epoch) -> Orbit: + """Adjusts the true anomaly of this orbit using the mean anomaly. + +# Astrodynamics note +This is not a true propagation of the orbit. This is akin to a two body propagation ONLY without any other force models applied. +Use Nyx for high fidelity propagation.""" + + def c3_km2_s2(self) -> float: + """Returns the $C_3$ of this orbit in km^2/s^2""" + + def declination_deg(self) -> float: + """Returns the declination of this orbit in degrees""" + + def distance_to_km(self, other: Orbit) -> float: + """Returns the distance in kilometers between this state and another state, if both frame match (epoch does not need to match).""" + + def ea_deg(self) -> float: + """Returns the eccentric anomaly in degrees + +This is a conversion from GMAT's StateConversionUtil::TrueToEccentricAnomaly""" + + def ecc(self) -> float: + """Returns the eccentricity (no unit)""" + + def energy_km2_s2(self) -> float: + """Returns the specific mechanical energy in km^2/s^2""" + + def eq_within(self, other: Orbit, radial_tol_km: float, velocity_tol_km_s: float) -> bool: + """Returns whether this orbit and another are equal within the specified radial and velocity absolute tolerances""" + + def fpa_deg(self) -> float: + """Returns the flight path angle in degrees""" + + @staticmethod + def from_cartesian(x_km: float, y_km: float, z_km: float, vx_km_s: float, vy_km_s: float, vz_km_s: float, epoch: Epoch, frame: Frame) -> Orbit: + """Creates a new Cartesian state in the provided frame at the provided Epoch. + +**Units:** km, km, km, km/s, km/s, km/s""" + + @staticmethod + def from_keplerian(sma_km: float, ecc: float, inc_deg: float, raan_deg: float, aop_deg: float, ta_deg: float, epoch: Epoch, frame: Frame) -> Orbit: + """Creates a new Orbit around the provided Celestial or Geoid frame from the Keplerian orbital elements. + +**Units:** km, none, degrees, degrees, degrees, degrees + +NOTE: The state is defined in Cartesian coordinates as they are non-singular. This causes rounding +errors when creating a state from its Keplerian orbital elements (cf. the state tests). +One should expect these errors to be on the order of 1e-12.""" + + @staticmethod + def from_keplerian_altitude(sma_altitude_km: float, ecc: float, inc_deg: float, raan_deg: float, aop_deg: float, ta_deg: float, epoch: Epoch, frame: Frame) -> Orbit: + """Creates a new Orbit from the provided semi-major axis altitude in kilometers""" + + @staticmethod + def from_keplerian_apsis_altitude(apo_alt_km: float, peri_alt_km: float, inc_deg: float, raan_deg: float, aop_deg: float, ta_deg: float, epoch: Epoch, frame: Frame) -> Orbit: + """Creates a new Orbit from the provided altitudes of apoapsis and periapsis, in kilometers""" + + @staticmethod + def from_keplerian_apsis_radii(r_a_km: float, r_p_km: float, inc_deg: float, raan_deg: float, aop_deg: float, ta_deg: float, epoch: Epoch, frame: Frame) -> Orbit: + """Attempts to create a new Orbit from the provided radii of apoapsis and periapsis, in kilometers""" + + @staticmethod + def from_keplerian_mean_anomaly(sma_km: float, ecc: float, inc_deg: float, raan_deg: float, aop_deg: float, ma_deg: float, epoch: Epoch, frame: Frame) -> Orbit: + """Initializes a new orbit from the Keplerian orbital elements using the mean anomaly instead of the true anomaly. + +# Implementation notes +This function starts by converting the mean anomaly to true anomaly, and then it initializes the orbit +using the keplerian(..) method. +The conversion is from GMAT's MeanToTrueAnomaly function, transliterated originally by Claude and GPT4 with human adjustments.""" + + @staticmethod + def from_latlongalt(latitude_deg: float, longitude_deg: float, height_km: float, angular_velocity: float, epoch: Epoch, frame: Frame) -> Orbit: + """Creates a new Orbit from the latitude (φ), longitude (λ) and height (in km) with respect to the frame's ellipsoid given the angular velocity. + +**Units:** degrees, degrees, km, rad/s +NOTE: This computation differs from the spherical coordinates because we consider the flattening of body. +Reference: G. Xu and Y. Xu, "GPS", DOI 10.1007/978-3-662-50367-6_2, 2016""" + + def height_km(self) -> float: + """Returns the geodetic height in km. + +Reference: Vallado, 4th Ed., Algorithm 12 page 172.""" + + def hmag(self) -> float: + """Returns the norm of the orbital momentum""" + + def hx(self) -> float: + """Returns the orbital momentum value on the X axis""" + + def hy(self) -> float: + """Returns the orbital momentum value on the Y axis""" + + def hyperbolic_anomaly_deg(self) -> float: + """Returns the hyperbolic anomaly in degrees between 0 and 360.0 +Returns an error if the orbit is not hyperbolic.""" + + def hz(self) -> float: + """Returns the orbital momentum value on the Z axis""" + + def inc_deg(self) -> float: + """Returns the inclination in degrees""" + + def is_brouwer_short_valid(self) -> bool: + """Returns whether this state satisfies the requirement to compute the Mean Brouwer Short orbital +element set. + +This is a conversion from GMAT's StateConversionUtil::CartesianToBrouwerMeanShort. +The details are at the log level `info`. +NOTE: Mean Brouwer Short are only defined around Earth. However, `nyx` does *not* check the +main celestial body around which the state is defined (GMAT does perform this verification).""" + + def latitude_deg(self) -> float: + """Returns the geodetic latitude (φ) in degrees. Value is between -180 and +180 degrees. + +# Frame warning +This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**.""" + + def latlongalt(self) -> typing.Tuple: + """Returns the geodetic latitude, geodetic longitude, and geodetic height, respectively in degrees, degrees, and kilometers. + +# Algorithm +This uses the Heikkinen procedure, which is not iterative. The results match Vallado and GMAT.""" + + def light_time(self) -> Duration: + """Returns the light time duration between this object and the origin of its reference frame.""" + + def longitude_360_deg(self) -> float: + """Returns the geodetic longitude (λ) in degrees. Value is between 0 and 360 degrees. + +# Frame warning +This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**.""" + + def longitude_deg(self) -> float: + """Returns the geodetic longitude (λ) in degrees. Value is between -180 and 180 degrees. + +# Frame warning +This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**.""" + + def ma_deg(self) -> float: + """Returns the mean anomaly in degrees + +This is a conversion from GMAT's StateConversionUtil::TrueToMeanAnomaly""" + + def periapsis_altitude_km(self) -> float: + """Returns the altitude of periapsis (or perigee around Earth), in kilometers.""" + + def periapsis_km(self) -> float: + """Returns the radius of periapsis (or perigee around Earth), in kilometers.""" + + def period(self) -> Duration: + """Returns the period in seconds""" + + def raan_deg(self) -> float: + """Returns the right ascension of the ascending node in degrees""" + + def rel_difference(self, other: Orbit) -> typing.Tuple: + """Returns the relative difference between this orbit and another for the position and velocity, respectively the first and second return values. +Both return values are UNITLESS because the relative difference is computed as the absolute difference divided by the rmag and vmag of this object. +Raises an error if the frames do not match, if the position is zero or the velocity is zero.""" + + def rel_pos_diff(self, other: Orbit) -> float: + """Returns the relative position difference (unitless) between this orbit and another. +This is computed by dividing the absolute difference by the norm of this object's radius vector. +If the radius is zero, this function raises a math error. +Raises an error if the frames do not match or (epochs do not need to match).""" + + def rel_vel_diff(self, other: Orbit) -> float: + """Returns the absolute velocity difference in kilometer per second between this orbit and another. +Raises an error if the frames do not match (epochs do not need to match).""" + + def ric_difference(self, other: Orbit) -> Orbit: + """Returns a Cartesian state representing the RIC difference between self and other, in position and velocity (with transport theorem). +Refer to dcm_from_ric_to_inertial for details on the RIC frame. + +# Algorithm +1. Compute the RIC DCM of self +2. Rotate self into the RIC frame +3. Rotation other into the RIC frame +4. Compute the difference between these two states +5. Strip the astrodynamical information from the frame, enabling only computations from `CartesianState`""" + + def right_ascension_deg(self) -> float: + """Returns the right ascension of this orbit in degrees""" + + def rmag_km(self) -> float: + """Returns the magnitude of the radius vector in km""" + + def rms_radius_km(self, other: Orbit) -> float: + """Returns the root sum squared (RMS) radius difference between this state and another state, if both frames match (epoch does not need to match)""" + + def rms_velocity_km_s(self, other: Orbit) -> float: + """Returns the root sum squared (RMS) velocity difference between this state and another state, if both frames match (epoch does not need to match)""" + + def rss_radius_km(self, other: Orbit) -> float: + """Returns the root mean squared (RSS) radius difference between this state and another state, if both frames match (epoch does not need to match)""" + + def rss_velocity_km_s(self, other: Orbit) -> float: + """Returns the root mean squared (RSS) velocity difference between this state and another state, if both frames match (epoch does not need to match)""" + + def semi_minor_axis_km(self) -> float: + """Returns the semi minor axis in km, includes code for a hyperbolic orbit""" + + def semi_parameter_km(self) -> float: + """Returns the semi parameter (or semilatus rectum)""" + + def set_aop_deg(self, new_aop_deg: float) -> None: + """Mutates this orbit to change the AOP""" + + def set_ecc(self, new_ecc: float) -> None: + """Mutates this orbit to change the ECC""" + + def set_inc_deg(self, new_inc_deg: float) -> None: + """Mutates this orbit to change the INC""" + + def set_raan_deg(self, new_raan_deg: float) -> None: + """Mutates this orbit to change the RAAN""" + + def set_sma_km(self, new_sma_km: float) -> None: + """Mutates this orbit to change the SMA""" + + def set_ta_deg(self, new_ta_deg: float) -> None: + """Mutates this orbit to change the TA""" + + def sma_altitude_km(self) -> float: + """Returns the SMA altitude in km""" + + def sma_km(self) -> float: + """Returns the semi-major axis in km""" + + def ta_deg(self) -> float: + """Returns the true anomaly in degrees between 0 and 360.0 + +NOTE: This function will emit a warning stating that the TA should be avoided if in a very near circular orbit +Code from + +LIMITATION: For an orbit whose true anomaly is (very nearly) 0.0 or 180.0, this function may return either 0.0 or 180.0 with a very small time increment. +This is due to the precision of the cosine calculation: if the arccosine calculation is out of bounds, the sign of the cosine of the true anomaly is used +to determine whether the true anomaly should be 0.0 or 180.0. **In other words**, there is an ambiguity in the computation in the true anomaly exactly at 180.0 and 0.0.""" + + def ta_dot_deg_s(self) -> float: + """Returns the time derivative of the true anomaly computed as the 360.0 degrees divided by the orbital period (in seconds).""" + + def tlong_deg(self) -> float: + """Returns the true longitude in degrees""" + + def velocity_declination_deg(self) -> float: + """Returns the velocity declination of this orbit in degrees""" + + def vinf_periapsis_km(self, turn_angle_degrees: float) -> float: + """Returns the radius of periapse in kilometers for the provided turn angle of this hyperbolic orbit. +Returns an error if the orbit is not hyperbolic.""" + + def vinf_turn_angle_deg(self, periapsis_km: float) -> float: + """Returns the turn angle in degrees for the provided radius of periapse passage of this hyperbolic orbit +Returns an error if the orbit is not hyperbolic.""" + + def vmag_km_s(self) -> float: + """Returns the magnitude of the velocity vector in km/s""" + + def vnc_difference(self, other: Orbit) -> Orbit: + """Returns a Cartesian state representing the VNC difference between self and other, in position and velocity (with transport theorem). +Refer to dcm_from_vnc_to_inertial for details on the VNC frame. + +# Algorithm +1. Compute the VNC DCM of self +2. Rotate self into the VNC frame +3. Rotation other into the VNC frame +4. Compute the difference between these two states +5. Strip the astrodynamical information from the frame, enabling only computations from `CartesianState`""" + + def with_aop_deg(self, new_aop_deg: float) -> Orbit: + """Returns a copy of the state with a new AOP""" + + def with_apoapsis_periapsis_km(self, new_ra_km: float, new_rp_km: float) -> Orbit: + """Returns a copy of this state with the provided apoasis and periapsis""" + + def with_ecc(self, new_ecc: float) -> Orbit: + """Returns a copy of the state with a new ECC""" + + def with_inc_deg(self, new_inc_deg: float) -> Orbit: + """Returns a copy of the state with a new INC""" + + def with_raan_deg(self, new_raan_deg: float) -> Orbit: + """Returns a copy of the state with a new RAAN""" + + def with_sma_km(self, new_sma_km: float) -> Orbit: + """Returns a copy of the state with a new SMA""" + + def with_ta_deg(self, new_ta_deg: float) -> Orbit: + """Returns a copy of the state with a new TA""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self) -> typing.Tuple:... + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" \ No newline at end of file diff --git a/anise/src/astro/orbit.rs b/anise/src/astro/orbit.rs index df6d8551..81e083f7 100644 --- a/anise/src/astro/orbit.rs +++ b/anise/src/astro/orbit.rs @@ -548,37 +548,61 @@ impl Orbit { /// NOTE: The state is defined in Cartesian coordinates as they are non-singular. This causes rounding /// errors when creating a state from its Keplerian orbital elements (cf. the state tests). /// One should expect these errors to be on the order of 1e-12. + /// + /// :type sma_km: float + /// :type ecc: float + /// :type inc_deg: float + /// :type raan_deg: float + /// :type aop_deg: float + /// :type ta_deg: float + /// :type epoch: Epoch + /// :type frame: Frame + /// :rtype: Orbit #[cfg(feature = "python")] #[classmethod] pub fn from_keplerian( _cls: &Bound<'_, PyType>, - sma: f64, + sma_km: f64, ecc: f64, - inc: f64, - raan: f64, - aop: f64, - ta: f64, + inc_deg: f64, + raan_deg: f64, + aop_deg: f64, + ta_deg: f64, epoch: Epoch, frame: Frame, ) -> PhysicsResult { - Self::try_keplerian(sma, ecc, inc, raan, aop, ta, epoch, frame) + Self::try_keplerian( + sma_km, ecc, inc_deg, raan_deg, aop_deg, ta_deg, epoch, frame, + ) } /// Attempts to create a new Orbit from the provided radii of apoapsis and periapsis, in kilometers + /// + /// :type r_a_km: float + /// :type r_p_km: float + /// :type inc_deg: float + /// :type raan_deg: float + /// :type aop_deg: float + /// :type ta_deg: float + /// :type epoch: Epoch + /// :type frame: Frame + /// :rtype: Orbit #[cfg(feature = "python")] #[classmethod] pub fn from_keplerian_apsis_radii( _cls: &Bound<'_, PyType>, - r_a: f64, - r_p: f64, - inc: f64, - raan: f64, - aop: f64, - ta: f64, + r_a_km: f64, + r_p_km: f64, + inc_deg: f64, + raan_deg: f64, + aop_deg: f64, + ta_deg: f64, epoch: Epoch, frame: Frame, ) -> PhysicsResult { - Self::try_keplerian_apsis_radii(r_a, r_p, inc, raan, aop, ta, epoch, frame) + Self::try_keplerian_apsis_radii( + r_a_km, r_p_km, inc_deg, raan_deg, aop_deg, ta_deg, epoch, frame, + ) } /// Initializes a new orbit from the Keplerian orbital elements using the mean anomaly instead of the true anomaly. @@ -587,6 +611,16 @@ impl Orbit { /// This function starts by converting the mean anomaly to true anomaly, and then it initializes the orbit /// using the keplerian(..) method. /// The conversion is from GMAT's MeanToTrueAnomaly function, transliterated originally by Claude and GPT4 with human adjustments. + /// + /// :type sma_km: float + /// :type ecc: float + /// :type inc_deg: float + /// :type raan_deg: float + /// :type aop_deg: float + /// :type ma_deg: float + /// :type epoch: Epoch + /// :type frame: Frame + /// :rtype: Orbit #[cfg(feature = "python")] #[classmethod] pub fn from_keplerian_mean_anomaly( @@ -606,26 +640,36 @@ impl Orbit { } /// Returns the orbital momentum value on the X axis + /// + /// :rtype: float pub fn hx(&self) -> PhysicsResult { Ok(self.hvec()?[0]) } /// Returns the orbital momentum value on the Y axis + /// + /// :rtype: float pub fn hy(&self) -> PhysicsResult { Ok(self.hvec()?[1]) } /// Returns the orbital momentum value on the Z axis + /// + /// :rtype: float pub fn hz(&self) -> PhysicsResult { Ok(self.hvec()?[2]) } /// Returns the norm of the orbital momentum + /// + /// :rtype: float pub fn hmag(&self) -> PhysicsResult { Ok(self.hvec()?.norm()) } /// Returns the specific mechanical energy in km^2/s^2 + /// + /// :rtype: float pub fn energy_km2_s2(&self) -> PhysicsResult { ensure!( self.rmag_km() > f64::EPSILON, @@ -637,12 +681,17 @@ impl Orbit { } /// Returns the semi-major axis in km + /// + /// :rtype: float pub fn sma_km(&self) -> PhysicsResult { // Division by zero prevented in energy_km2_s2 Ok(-self.frame.mu_km3_s2()? / (2.0 * self.energy_km2_s2()?)) } /// Mutates this orbit to change the SMA + /// + /// :type new_sma_km: float + /// :rtype: None pub fn set_sma_km(&mut self, new_sma_km: f64) -> PhysicsResult<()> { let me = Self::try_keplerian( new_sma_km, @@ -661,6 +710,9 @@ impl Orbit { } /// Returns a copy of the state with a new SMA + /// + /// :type new_sma_km: float + /// :rtype: Orbit pub fn with_sma_km(&self, new_sma_km: f64) -> PhysicsResult { let mut me = *self; me.set_sma_km(new_sma_km)?; @@ -668,13 +720,18 @@ impl Orbit { } /// Returns a copy of the state with a provided SMA added to the current one - pub fn add_sma_km(&self, delta_sma: f64) -> PhysicsResult { + /// + /// :type delta_sma_km: float + /// :rtype: Orbit + pub fn add_sma_km(&self, delta_sma_km: f64) -> PhysicsResult { let mut me = *self; - me.set_sma_km(me.sma_km()? + delta_sma)?; + me.set_sma_km(me.sma_km()? + delta_sma_km)?; Ok(me) } /// Returns the period in seconds + /// + /// :rtype: Duration pub fn period(&self) -> PhysicsResult { Ok(2.0 * PI @@ -684,11 +741,16 @@ impl Orbit { } /// Returns the eccentricity (no unit) + /// + /// :rtype: float pub fn ecc(&self) -> PhysicsResult { Ok(self.evec()?.norm()) } /// Mutates this orbit to change the ECC + /// + /// :type new_ecc: float + /// :rtype: None pub fn set_ecc(&mut self, new_ecc: f64) -> PhysicsResult<()> { let me = Self::try_keplerian( self.sma_km()?, @@ -707,6 +769,9 @@ impl Orbit { } /// Returns a copy of the state with a new ECC + /// + /// :type new_ecc: float + /// :rtype: Orbit pub fn with_ecc(&self, new_ecc: f64) -> PhysicsResult { let mut me = *self; me.set_ecc(new_ecc)?; @@ -714,6 +779,9 @@ impl Orbit { } /// Returns a copy of the state with a provided ECC added to the current one + /// + /// :type delta_ecc: float + /// :rtype: Orbit pub fn add_ecc(&self, delta_ecc: f64) -> PhysicsResult { let mut me = *self; me.set_ecc(me.ecc()? + delta_ecc)?; @@ -721,11 +789,16 @@ impl Orbit { } /// Returns the inclination in degrees + /// + /// :rtype: float pub fn inc_deg(&self) -> PhysicsResult { Ok((self.hvec()?[2] / self.hmag()?).acos().to_degrees()) } /// Mutates this orbit to change the INC + /// + /// :type new_inc_deg: float + /// :rtype: None pub fn set_inc_deg(&mut self, new_inc_deg: f64) -> PhysicsResult<()> { let me = Self::try_keplerian( self.sma_km()?, @@ -744,6 +817,9 @@ impl Orbit { } /// Returns a copy of the state with a new INC + /// + /// :type new_inc_deg: float + /// :rtype: Orbit pub fn with_inc_deg(&self, new_inc_deg: f64) -> PhysicsResult { let mut me = *self; me.set_inc_deg(new_inc_deg)?; @@ -751,6 +827,9 @@ impl Orbit { } /// Returns a copy of the state with a provided INC added to the current one + /// + /// :type delta_inc_deg: float + /// :rtype: None pub fn add_inc_deg(&self, delta_inc_deg: f64) -> PhysicsResult { let mut me = *self; me.set_inc_deg(me.inc_deg()? + delta_inc_deg)?; @@ -758,6 +837,8 @@ impl Orbit { } /// Returns the argument of periapsis in degrees + /// + /// :rtype: float pub fn aop_deg(&self) -> PhysicsResult { let n = Vector3::new(0.0, 0.0, 1.0).cross(&self.hvec()?); let cos_aop = n.dot(&self.evec()?) / (n.norm() * self.ecc()?); @@ -776,6 +857,9 @@ impl Orbit { } /// Mutates this orbit to change the AOP + /// + /// :type new_aop_deg: float + /// :rtype: None pub fn set_aop_deg(&mut self, new_aop_deg: f64) -> PhysicsResult<()> { let me = Self::try_keplerian( self.sma_km()?, @@ -794,6 +878,9 @@ impl Orbit { } /// Returns a copy of the state with a new AOP + /// + /// :type new_aop_deg: float + /// :rtype: Orbit pub fn with_aop_deg(&self, new_aop_deg: f64) -> PhysicsResult { let mut me = *self; me.set_aop_deg(new_aop_deg)?; @@ -801,6 +888,9 @@ impl Orbit { } /// Returns a copy of the state with a provided AOP added to the current one + /// + /// :type delta_aop_deg: float + /// :rtype: Orbit pub fn add_aop_deg(&self, delta_aop_deg: f64) -> PhysicsResult { let mut me = *self; me.set_aop_deg(me.aop_deg()? + delta_aop_deg)?; @@ -808,6 +898,8 @@ impl Orbit { } /// Returns the right ascension of the ascending node in degrees + /// + /// :rtype: float pub fn raan_deg(&self) -> PhysicsResult { let n = Vector3::new(0.0, 0.0, 1.0).cross(&self.hvec()?); let cos_raan = n[0] / n.norm(); @@ -826,6 +918,9 @@ impl Orbit { } /// Mutates this orbit to change the RAAN + /// + /// :type new_raan_deg: float + /// :rtype: None pub fn set_raan_deg(&mut self, new_raan_deg: f64) -> PhysicsResult<()> { let me = Self::try_keplerian( self.sma_km()?, @@ -844,6 +939,9 @@ impl Orbit { } /// Returns a copy of the state with a new RAAN + /// + /// :type new_raan_deg: float + /// :rtype: Orbit pub fn with_raan_deg(&self, new_raan_deg: f64) -> PhysicsResult { let mut me = *self; me.set_raan_deg(new_raan_deg)?; @@ -851,6 +949,9 @@ impl Orbit { } /// Returns a copy of the state with a provided RAAN added to the current one + /// + /// :type delta_raan_deg: float + /// :rtype: Orbit pub fn add_raan_deg(&self, delta_raan_deg: f64) -> PhysicsResult { let mut me = *self; me.set_raan_deg(me.raan_deg()? + delta_raan_deg)?; @@ -865,6 +966,8 @@ impl Orbit { /// LIMITATION: For an orbit whose true anomaly is (very nearly) 0.0 or 180.0, this function may return either 0.0 or 180.0 with a very small time increment. /// This is due to the precision of the cosine calculation: if the arccosine calculation is out of bounds, the sign of the cosine of the true anomaly is used /// to determine whether the true anomaly should be 0.0 or 180.0. **In other words**, there is an ambiguity in the computation in the true anomaly exactly at 180.0 and 0.0. + /// + /// :rtype: float pub fn ta_deg(&self) -> PhysicsResult { if self.ecc()? < ECC_EPSILON { warn!( @@ -889,6 +992,9 @@ impl Orbit { } /// Mutates this orbit to change the TA + /// + /// :type new_ta_deg: float + /// :rtype: None pub fn set_ta_deg(&mut self, new_ta_deg: f64) -> PhysicsResult<()> { let me = Self::try_keplerian( self.sma_km()?, @@ -907,11 +1013,16 @@ impl Orbit { } /// Returns the time derivative of the true anomaly computed as the 360.0 degrees divided by the orbital period (in seconds). + /// + /// :rtype: float pub fn ta_dot_deg_s(&self) -> PhysicsResult { Ok(360.0 / self.period()?.to_seconds()) } /// Returns a copy of the state with a new TA + /// + /// :type new_ta_deg: float + /// :rtype: Orbit pub fn with_ta_deg(&self, new_ta_deg: f64) -> PhysicsResult { let mut me = *self; me.set_ta_deg(new_ta_deg)?; @@ -919,6 +1030,9 @@ impl Orbit { } /// Returns a copy of the state with a provided TA added to the current one + /// + /// :type delta_ta_deg: float + /// :rtype: Orbit pub fn add_ta_deg(&self, delta_ta_deg: f64) -> PhysicsResult { let mut me = *self; me.set_ta_deg(me.ta_deg()? + delta_ta_deg)?; @@ -926,6 +1040,10 @@ impl Orbit { } /// Returns a copy of this state with the provided apoasis and periapsis + /// + /// :type new_ra_km: float + /// :type new_rp_km: float + /// :rtype: Orbit pub fn with_apoapsis_periapsis_km( &self, new_ra_km: f64, @@ -944,6 +1062,10 @@ impl Orbit { } /// Returns a copy of this state with the provided apoasis and periapsis added to the current values + /// + /// :type delta_ra_km: float + /// :type delta_rp_km: float + /// :rtype: Orbit pub fn add_apoapsis_periapsis_km( &self, delta_ra_km: f64, @@ -962,6 +1084,8 @@ impl Orbit { } /// Returns the true longitude in degrees + /// + /// :rtype: float pub fn tlong_deg(&self) -> PhysicsResult { // Angles already in degrees Ok(between_0_360( @@ -973,6 +1097,8 @@ impl Orbit { /// /// NOTE: If the orbit is near circular, the AoL will be computed from the true longitude /// instead of relying on the ill-defined true anomaly. + /// + /// :rtype: float pub fn aol_deg(&self) -> PhysicsResult { Ok(between_0_360(if self.ecc()? < ECC_EPSILON { self.tlong_deg()? - self.raan_deg()? @@ -982,11 +1108,15 @@ impl Orbit { } /// Returns the radius of periapsis (or perigee around Earth), in kilometers. + /// + /// :rtype: float pub fn periapsis_km(&self) -> PhysicsResult { Ok(self.sma_km()? * (1.0 - self.ecc()?)) } /// Returns the radius of apoapsis (or apogee around Earth), in kilometers. + /// + /// :rtype: float pub fn apoapsis_km(&self) -> PhysicsResult { Ok(self.sma_km()? * (1.0 + self.ecc()?)) } @@ -994,6 +1124,8 @@ impl Orbit { /// Returns the eccentric anomaly in degrees /// /// This is a conversion from GMAT's StateConversionUtil::TrueToEccentricAnomaly + /// + /// :rtype: float pub fn ea_deg(&self) -> PhysicsResult { let (sin_ta, cos_ta) = self.ta_deg()?.to_radians().sin_cos(); let ecc_cos_ta = self.ecc()? * cos_ta; @@ -1004,6 +1136,8 @@ impl Orbit { } /// Returns the flight path angle in degrees + /// + /// :rtype: float pub fn fpa_deg(&self) -> PhysicsResult { let nu = self.ta_deg()?.to_radians(); let ecc = self.ecc()?; @@ -1016,6 +1150,8 @@ impl Orbit { /// Returns the mean anomaly in degrees /// /// This is a conversion from GMAT's StateConversionUtil::TrueToMeanAnomaly + /// + /// :rtype: float pub fn ma_deg(&self) -> PhysicsResult { if self.ecc()?.abs() < ECC_EPSILON { Err(PhysicsError::ParabolicEccentricity { limit: ECC_EPSILON }) @@ -1036,6 +1172,8 @@ impl Orbit { } /// Returns the semi parameter (or semilatus rectum) + /// + /// :rtype: float pub fn semi_parameter_km(&self) -> PhysicsResult { Ok(self.sma_km()? * (1.0 - self.ecc()?.powi(2))) } @@ -1047,6 +1185,8 @@ impl Orbit { /// The details are at the log level `info`. /// NOTE: Mean Brouwer Short are only defined around Earth. However, `nyx` does *not* check the /// main celestial body around which the state is defined (GMAT does perform this verification). + /// + /// :rtype: bool pub fn is_brouwer_short_valid(&self) -> PhysicsResult { if self.inc_deg()? > 180.0 { info!("Brouwer Mean Short only applicable for inclinations less than 180.0"); @@ -1064,16 +1204,22 @@ impl Orbit { } /// Returns the right ascension of this orbit in degrees + /// + /// :rtype: float pub fn right_ascension_deg(&self) -> f64 { between_0_360((self.radius_km.y.atan2(self.radius_km.x)).to_degrees()) } /// Returns the declination of this orbit in degrees + /// + /// :rtype: float pub fn declination_deg(&self) -> f64 { between_pm_180((self.radius_km.z / self.rmag_km()).asin().to_degrees()) } /// Returns the semi minor axis in km, includes code for a hyperbolic orbit + /// + /// :rtype: float pub fn semi_minor_axis_km(&self) -> PhysicsResult { if self.ecc()? <= 1.0 { Ok(((self.sma_km()? * self.ecc()?).powi(2) - self.sma_km()?.powi(2)).sqrt()) @@ -1084,6 +1230,8 @@ impl Orbit { } /// Returns the velocity declination of this orbit in degrees + /// + /// :rtype: float pub fn velocity_declination_deg(&self) -> f64 { between_pm_180( (self.velocity_km_s.z / self.vmag_km_s()) @@ -1093,12 +1241,17 @@ impl Orbit { } /// Returns the $C_3$ of this orbit in km^2/s^2 + /// + /// :rtype: float pub fn c3_km2_s2(&self) -> PhysicsResult { Ok(-self.frame.mu_km3_s2()? / self.sma_km()?) } /// Returns the radius of periapse in kilometers for the provided turn angle of this hyperbolic orbit. /// Returns an error if the orbit is not hyperbolic. + /// + /// :type turn_angle_degrees: float + /// :rtype: float pub fn vinf_periapsis_km(&self, turn_angle_degrees: f64) -> PhysicsResult { let ecc = self.ecc()?; if ecc <= 1.0 { @@ -1113,6 +1266,9 @@ impl Orbit { /// Returns the turn angle in degrees for the provided radius of periapse passage of this hyperbolic orbit /// Returns an error if the orbit is not hyperbolic. + /// + /// :type periapsis_km: float + /// :rtype: float pub fn vinf_turn_angle_deg(&self, periapsis_km: f64) -> PhysicsResult { let ecc = self.ecc()?; if ecc <= 1.0 { @@ -1129,6 +1285,8 @@ impl Orbit { /// Returns the hyperbolic anomaly in degrees between 0 and 360.0 /// Returns an error if the orbit is not hyperbolic. + /// + /// :rtype: float pub fn hyperbolic_anomaly_deg(&self) -> PhysicsResult { let ecc = self.ecc()?; if ecc <= 1.0 { @@ -1148,6 +1306,8 @@ impl Orbit { /// This is not a true propagation of the orbit. This is akin to a two body propagation ONLY without any other force models applied. /// Use Nyx for high fidelity propagation. /// + /// :type new_epoch: Epoch + /// :rtype: Orbit pub fn at_epoch(&self, new_epoch: Epoch) -> PhysicsResult { let m0_rad = self.ma_deg()?.to_radians(); let mt_rad = m0_rad @@ -1175,6 +1335,9 @@ impl Orbit { /// 3. Rotation other into the RIC frame /// 4. Compute the difference between these two states /// 5. Strip the astrodynamical information from the frame, enabling only computations from `CartesianState` + /// + /// :type other: Orbit + /// :rtype: Orbit pub fn ric_difference(&self, other: &Self) -> PhysicsResult { let self_in_ric = (self.dcm_from_ric_to_inertial()?.transpose() * self)?; let other_in_ric = (self.dcm_from_ric_to_inertial()?.transpose() * other)?; @@ -1192,6 +1355,9 @@ impl Orbit { /// 3. Rotation other into the VNC frame /// 4. Compute the difference between these two states /// 5. Strip the astrodynamical information from the frame, enabling only computations from `CartesianState` + /// + /// :type other: Orbit + /// :rtype: Orbit pub fn vnc_difference(&self, other: &Self) -> PhysicsResult { let self_in_vnc = (self.dcm_from_vnc_to_inertial()?.transpose() * self)?; let other_in_vnc = (self.dcm_from_vnc_to_inertial()?.transpose() * other)?; diff --git a/anise/src/astro/orbit_geodetic.rs b/anise/src/astro/orbit_geodetic.rs index db3e8596..0208331c 100644 --- a/anise/src/astro/orbit_geodetic.rs +++ b/anise/src/astro/orbit_geodetic.rs @@ -28,22 +28,22 @@ impl CartesianState { /// Creates a new Orbit from the provided semi-major axis altitude in kilometers #[allow(clippy::too_many_arguments)] pub fn try_keplerian_altitude( - sma_altitude: f64, + sma_altitude_km: f64, ecc: f64, - inc: f64, - raan: f64, - aop: f64, - ta: f64, + inc_deg: f64, + raan_deg: f64, + aop_deg: f64, + ta_deg: f64, epoch: Epoch, frame: Frame, ) -> PhysicsResult { Self::try_keplerian( - sma_altitude + frame.mean_equatorial_radius_km()?, + sma_altitude_km + frame.mean_equatorial_radius_km()?, ecc, - inc, - raan, - aop, - ta, + inc_deg, + raan_deg, + aop_deg, + ta_deg, epoch, frame, ) @@ -52,22 +52,22 @@ impl CartesianState { /// Creates a new Orbit from the provided altitudes of apoapsis and periapsis, in kilometers #[allow(clippy::too_many_arguments)] pub fn try_keplerian_apsis_altitude( - apo_alt: f64, - peri_alt: f64, - inc: f64, - raan: f64, - aop: f64, - ta: f64, + apo_alt_km: f64, + peri_alt_km: f64, + inc_deg: f64, + raan_deg: f64, + aop_deg: f64, + ta_deg: f64, epoch: Epoch, frame: Frame, ) -> PhysicsResult { Self::try_keplerian_apsis_radii( - apo_alt + frame.mean_equatorial_radius_km()?, - peri_alt + frame.mean_equatorial_radius_km()?, - inc, - raan, - aop, - ta, + apo_alt_km + frame.mean_equatorial_radius_km()?, + peri_alt_km + frame.mean_equatorial_radius_km()?, + inc_deg, + raan_deg, + aop_deg, + ta_deg, epoch, frame, ) @@ -115,39 +115,77 @@ impl CartesianState { #[cfg_attr(feature = "python", pymethods)] impl CartesianState { /// Creates a new Orbit from the provided semi-major axis altitude in kilometers + /// + /// :type sma_altitude_km: float + /// :type ecc: float + /// :type inc_deg: float + /// :type raan_deg: float + /// :type aop_deg: float + /// :type ta_deg: float + /// :type epoch: Epoch + /// :type frame: Frame + /// :rtype: Orbit #[allow(clippy::too_many_arguments)] #[cfg(feature = "python")] #[classmethod] pub fn from_keplerian_altitude( _cls: &Bound<'_, PyType>, - sma_altitude: f64, + sma_altitude_km: f64, ecc: f64, - inc: f64, - raan: f64, - aop: f64, - ta: f64, + inc_deg: f64, + raan_deg: f64, + aop_deg: f64, + ta_deg: f64, epoch: Epoch, frame: Frame, ) -> PhysicsResult { - Self::try_keplerian_altitude(sma_altitude, ecc, inc, raan, aop, ta, epoch, frame) + Self::try_keplerian_altitude( + sma_altitude_km, + ecc, + inc_deg, + raan_deg, + aop_deg, + ta_deg, + epoch, + frame, + ) } /// Creates a new Orbit from the provided altitudes of apoapsis and periapsis, in kilometers + /// + /// :type apo_alt_km: float + /// :type peri_alt_km: float + /// :type inc_deg: float + /// :type raan_deg: float + /// :type aop_deg: float + /// :type ta_deg: float + /// :type epoch: Epoch + /// :type frame: Frame + /// :rtype: Orbit #[allow(clippy::too_many_arguments)] #[cfg(feature = "python")] #[classmethod] pub fn from_keplerian_apsis_altitude( _cls: &Bound<'_, PyType>, - apo_alt: f64, - peri_alt: f64, - inc: f64, - raan: f64, - aop: f64, - ta: f64, + apo_alt_km: f64, + peri_alt_km: f64, + inc_deg: f64, + raan_deg: f64, + aop_deg: f64, + ta_deg: f64, epoch: Epoch, frame: Frame, ) -> PhysicsResult { - Self::try_keplerian_apsis_altitude(apo_alt, peri_alt, inc, raan, aop, ta, epoch, frame) + Self::try_keplerian_apsis_altitude( + apo_alt_km, + peri_alt_km, + inc_deg, + raan_deg, + aop_deg, + ta_deg, + epoch, + frame, + ) } /// Creates a new Orbit from the latitude (φ), longitude (λ) and height (in km) with respect to the frame's ellipsoid given the angular velocity. @@ -155,6 +193,15 @@ impl CartesianState { /// **Units:** degrees, degrees, km, rad/s /// NOTE: This computation differs from the spherical coordinates because we consider the flattening of body. /// Reference: G. Xu and Y. Xu, "GPS", DOI 10.1007/978-3-662-50367-6_2, 2016 + /// + /// + /// :type latitude_deg: float + /// :type longitude_deg: float + /// :type height_km: float + /// :type angular_velocity: float + /// :type epoch: Epoch + /// :type frame: Frame + /// :rtype: Orbit #[cfg(feature = "python")] #[classmethod] pub fn from_latlongalt( @@ -177,16 +224,22 @@ impl CartesianState { } /// Returns the SMA altitude in km + /// + /// :rtype: float pub fn sma_altitude_km(&self) -> PhysicsResult { Ok(self.sma_km()? - self.frame.mean_equatorial_radius_km()?) } /// Returns the altitude of periapsis (or perigee around Earth), in kilometers. + /// + /// :rtype: float pub fn periapsis_altitude_km(&self) -> PhysicsResult { Ok(self.periapsis_km()? - self.frame.mean_equatorial_radius_km()?) } /// Returns the altitude of apoapsis (or apogee around Earth), in kilometers. + /// + /// :rtype: float pub fn apoapsis_altitude_km(&self) -> PhysicsResult { Ok(self.apoapsis_km()? - self.frame.mean_equatorial_radius_km()?) } @@ -196,6 +249,7 @@ impl CartesianState { /// # Algorithm /// This uses the Heikkinen procedure, which is not iterative. The results match Vallado and GMAT. /// + /// :rtype: typing.Tuple pub fn latlongalt(&self) -> PhysicsResult<(f64, f64, f64)> { let a_km = self.frame.mean_equatorial_radius_km()?; let b_km = self.frame.shape.unwrap().polar_radius_km; @@ -231,6 +285,8 @@ impl CartesianState { /// /// # Frame warning /// This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**. + /// + /// :rtype: float pub fn longitude_deg(&self) -> f64 { between_pm_180(self.radius_km.y.atan2(self.radius_km.x).to_degrees()) } @@ -239,6 +295,8 @@ impl CartesianState { /// /// # Frame warning /// This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**. + /// + /// :rtype: float pub fn longitude_360_deg(&self) -> f64 { between_0_360(self.radius_km.y.atan2(self.radius_km.x).to_degrees()) } @@ -247,6 +305,8 @@ impl CartesianState { /// /// # Frame warning /// This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**. + /// + /// :rtype: float pub fn latitude_deg(&self) -> PhysicsResult { Ok(self.latlongalt()?.0) } @@ -254,6 +314,8 @@ impl CartesianState { /// Returns the geodetic height in km. /// /// Reference: Vallado, 4th Ed., Algorithm 12 page 172. + /// + /// :rtype: float pub fn height_km(&self) -> PhysicsResult { Ok(self.latlongalt()?.2) } diff --git a/anise/src/math/cartesian.rs b/anise/src/math/cartesian.rs index 0908f6bd..9ecccf4f 100644 --- a/anise/src/math/cartesian.rs +++ b/anise/src/math/cartesian.rs @@ -30,6 +30,16 @@ use pyo3::prelude::*; /// Regardless of the constructor used, this struct stores all the state information in Cartesian coordinates as these are always non singular. /// /// Unless noted otherwise, algorithms are from GMAT 2016a [StateConversionUtil.cpp](https://github.com/ChristopherRabotin/GMAT/blob/37201a6290e7f7b941bc98ee973a527a5857104b/src/base/util/StateConversionUtil.cpp). +/// +/// :type x_km: float +/// :type y_km: float +/// :type z_km: float +/// :type vx_km_s: float +/// :type vy_km_s: float +/// :type vz_km_s: float +/// :type epoch: Epoch +/// :type frame: Frame +/// :rtype: Orbit #[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[cfg_attr(feature = "python", pyclass(name = "Orbit"))] #[cfg_attr(feature = "python", pyo3(module = "anise.astro"))] @@ -218,16 +228,23 @@ impl CartesianState { #[cfg_attr(feature = "python", pymethods)] impl CartesianState { /// Returns the magnitude of the radius vector in km + /// + /// :rtype: float pub fn rmag_km(&self) -> f64 { self.radius_km.norm() } /// Returns the magnitude of the velocity vector in km/s + /// + /// :rtype: float pub fn vmag_km_s(&self) -> f64 { self.velocity_km_s.norm() } /// Returns the distance in kilometers between this state and another state, if both frame match (epoch does not need to match). + /// + /// :type other: Orbit + /// :rtype: float pub fn distance_to_km(&self, other: &Self) -> PhysicsResult { ensure!( self.frame.ephem_origin_match(other.frame) @@ -243,6 +260,9 @@ impl CartesianState { } /// Returns the root mean squared (RSS) radius difference between this state and another state, if both frames match (epoch does not need to match) + /// + /// :type other: Orbit + /// :rtype: float pub fn rss_radius_km(&self, other: &Self) -> PhysicsResult { ensure!( self.frame.ephem_origin_match(other.frame) @@ -257,6 +277,9 @@ impl CartesianState { } /// Returns the root mean squared (RSS) velocity difference between this state and another state, if both frames match (epoch does not need to match) + /// + /// :type other: Orbit + /// :rtype: float pub fn rss_velocity_km_s(&self, other: &Self) -> PhysicsResult { ensure!( self.frame.ephem_origin_match(other.frame) @@ -271,6 +294,9 @@ impl CartesianState { } /// Returns the root sum squared (RMS) radius difference between this state and another state, if both frames match (epoch does not need to match) + /// + /// :type other: Orbit + /// :rtype: float pub fn rms_radius_km(&self, other: &Self) -> PhysicsResult { ensure!( self.frame.ephem_origin_match(other.frame) @@ -285,6 +311,9 @@ impl CartesianState { } /// Returns the root sum squared (RMS) velocity difference between this state and another state, if both frames match (epoch does not need to match) + /// + /// :type other: Orbit + /// :rtype: float pub fn rms_velocity_km_s(&self, other: &Self) -> PhysicsResult { ensure!( self.frame.ephem_origin_match(other.frame) @@ -299,6 +328,11 @@ impl CartesianState { } /// Returns whether this orbit and another are equal within the specified radial and velocity absolute tolerances + /// + /// :type other: Orbit + /// :type radial_tol_km: float + /// :type velocity_tol_km_s: float + /// :rtype: bool pub fn eq_within(&self, other: &Self, radial_tol_km: f64, velocity_tol_km_s: f64) -> bool { self.epoch == other.epoch && (self.radius_km.x - other.radius_km.x).abs() < radial_tol_km @@ -312,12 +346,17 @@ impl CartesianState { } /// Returns the light time duration between this object and the origin of its reference frame. + /// + /// :rtype: Duration pub fn light_time(&self) -> Duration { (self.radius_km.norm() / SPEED_OF_LIGHT_KM_S).seconds() } /// Returns the absolute position difference in kilometer between this orbit and another. /// Raises an error if the frames do not match (epochs do not need to match). + /// + /// :type other: Orbit + /// :rtype: float pub fn abs_pos_diff_km(&self, other: &Self) -> PhysicsResult { ensure!( self.frame.ephem_origin_match(other.frame) @@ -334,6 +373,9 @@ impl CartesianState { /// Returns the absolute velocity difference in kilometer per second between this orbit and another. /// Raises an error if the frames do not match (epochs do not need to match). + /// + /// :type other: Orbit + /// :rtype: float pub fn abs_vel_diff_km_s(&self, other: &Self) -> PhysicsResult { ensure!( self.frame.ephem_origin_match(other.frame) @@ -350,6 +392,9 @@ impl CartesianState { /// Returns the absolute position and velocity differences in km and km/s between this orbit and another. /// Raises an error if the frames do not match (epochs do not need to match). + /// + /// :type other: Orbit + /// :rtype: typing.Tuple pub fn abs_difference(&self, other: &Self) -> PhysicsResult<(f64, f64)> { Ok((self.abs_pos_diff_km(other)?, self.abs_vel_diff_km_s(other)?)) } @@ -358,6 +403,9 @@ impl CartesianState { /// This is computed by dividing the absolute difference by the norm of this object's radius vector. /// If the radius is zero, this function raises a math error. /// Raises an error if the frames do not match or (epochs do not need to match). + /// + /// :type other: Orbit + /// :rtype: float pub fn rel_pos_diff(&self, other: &Self) -> PhysicsResult { if self.rmag_km() <= f64::EPSILON { return Err(PhysicsError::AppliedMath { @@ -372,6 +420,9 @@ impl CartesianState { /// Returns the absolute velocity difference in kilometer per second between this orbit and another. /// Raises an error if the frames do not match (epochs do not need to match). + /// + /// :type other: Orbit + /// :rtype: float pub fn rel_vel_diff(&self, other: &Self) -> PhysicsResult { if self.vmag_km_s() <= f64::EPSILON { return Err(PhysicsError::AppliedMath { @@ -387,6 +438,9 @@ impl CartesianState { /// Returns the relative difference between this orbit and another for the position and velocity, respectively the first and second return values. /// Both return values are UNITLESS because the relative difference is computed as the absolute difference divided by the rmag and vmag of this object. /// Raises an error if the frames do not match, if the position is zero or the velocity is zero. + /// + /// :type other: Orbit + /// :rtype: typing.Tuple pub fn rel_difference(&self, other: &Self) -> PhysicsResult<(f64, f64)> { Ok((self.rel_pos_diff(other)?, self.rel_vel_diff(other)?)) } diff --git a/anise/src/math/cartesian_py.rs b/anise/src/math/cartesian_py.rs index 6faaebea..f7650cb2 100644 --- a/anise/src/math/cartesian_py.rs +++ b/anise/src/math/cartesian_py.rs @@ -23,7 +23,16 @@ impl CartesianState { /// Creates a new Cartesian state in the provided frame at the provided Epoch. /// /// **Units:** km, km, km, km/s, km/s, km/s - + /// + /// :type x_km: float + /// :type y_km: float + /// :type z_km: float + /// :type vx_km_s: float + /// :type vy_km_s: float + /// :type vz_km_s: float + /// :type epoch: Epoch + /// :type frame: Frame + /// :rtype: Orbit #[allow(clippy::too_many_arguments)] #[classmethod] pub fn from_cartesian( @@ -43,7 +52,6 @@ impl CartesianState { /// Creates a new Cartesian state in the provided frame at the provided Epoch (calls from_cartesian). /// /// **Units:** km, km, km, km/s, km/s, km/s - #[allow(clippy::too_many_arguments)] #[new] pub fn py_new( @@ -59,71 +67,91 @@ impl CartesianState { Self::new(x_km, y_km, z_km, vx_km_s, vy_km_s, vz_km_s, epoch, frame) } + /// :rtype: float #[getter] fn get_x_km(&self) -> PyResult { Ok(self.radius_km[0]) } - + /// :type x_km: float + /// :rtype: None #[setter] fn set_x_km(&mut self, x_km: f64) -> PyResult<()> { self.radius_km[0] = x_km; Ok(()) } + /// :rtype: float #[getter] fn get_y_km(&self) -> PyResult { Ok(self.radius_km[1]) } - + /// :type y_km: float + /// :rtype: None #[setter] fn set_y_km(&mut self, y_km: f64) -> PyResult<()> { self.radius_km[1] = y_km; Ok(()) } + /// :rtype: float #[getter] fn get_z_km(&self) -> PyResult { Ok(self.radius_km[2]) } + /// :type z_km: float + /// :rtype: None + #[setter] + fn set_z_km(&mut self, z_km: f64) -> PyResult<()> { + self.radius_km[2] = z_km; + Ok(()) + } + /// :rtype: float #[getter] fn get_vx_km_s(&self) -> PyResult { Ok(self.velocity_km_s[0]) } - + /// :type vx_km_s: float + /// :rtype: None #[setter] - fn set_vx_km_s(&mut self, x_km: f64) -> PyResult<()> { - self.velocity_km_s[0] = x_km; + fn set_vx_km_s(&mut self, vx_km_s: f64) -> PyResult<()> { + self.velocity_km_s[0] = vx_km_s; Ok(()) } + /// :rtype: float #[getter] fn get_vy_km_s(&self) -> PyResult { Ok(self.velocity_km_s[1]) } - + /// :type vy_km_s: float + /// :rtype: None #[setter] - fn set_vy_km_s(&mut self, y_km: f64) -> PyResult<()> { - self.velocity_km_s[1] = y_km; + fn set_vy_km_s(&mut self, vy_km_s: f64) -> PyResult<()> { + self.velocity_km_s[1] = vy_km_s; Ok(()) } + /// :rtype: float #[getter] fn get_vz_km_s(&self) -> PyResult { Ok(self.velocity_km_s[2]) } - + /// :type vz_km_s: float + /// :rtype: None #[setter] - fn set_z_km(&mut self, z_km: f64) -> PyResult<()> { - self.radius_km[2] = z_km; + fn set_vz_km(&mut self, vz_km_s: f64) -> PyResult<()> { + self.velocity_km_s[2] = vz_km_s; Ok(()) } + /// :rtype: Epoch #[getter] fn get_epoch(&self) -> PyResult { Ok(self.epoch) } + /// :rtype: Frame #[getter] fn get_frame(&self) -> PyResult { Ok(self.frame) From d7ea97e4db020347eb5849e91b1f3b31964ae985 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Fri, 1 Nov 2024 21:51:57 -0600 Subject: [PATCH 5/6] Merge type hints --- anise-py/anise.pyi | 1793 ++++++++++++++++++++++++++++++++++++++++- anise-py/hifitime.pyi | 1087 +++++++++++++++++++++++++ 2 files changed, 2862 insertions(+), 18 deletions(-) create mode 100644 anise-py/hifitime.pyi diff --git a/anise-py/anise.pyi b/anise-py/anise.pyi index a040b59b..429e18a1 100644 --- a/anise-py/anise.pyi +++ b/anise-py/anise.pyi @@ -404,26 +404,1782 @@ This function modified `self` and changes the URI to be the path to the download @typing.final class astro: - AzElRange: type = ... - Ellipsoid: type = ... - Frame: type = ... - Orbit: type = ... - __all__: list = ... - __name__: str = ... - astro.constants: module = ... + @typing.final + class AzElRange: + """A structure that stores the result of Azimuth, Elevation, Range, Range rate calculation.""" + azimuth_deg: float + elevation_deg: float + epoch: Epoch + light_time: Duration + obstructed_by: Frame + range_km: float + range_rate_km_s: float + + def __init__(self, epoch: Epoch, azimuth_deg: float, elevation_deg: float, range_km: float, range_rate_km_s: float, obstructed_by: Frame=None) -> AzElRange: + """A structure that stores the result of Azimuth, Elevation, Range, Range rate calculation.""" + + def is_obstructed(self) -> bool: + """Returns whether there is an obstruction.""" + + def is_valid(self) -> bool: + """Returns false if the range is less than one millimeter, or any of the angles are NaN.""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self) -> typing.Tuple: + """Allows for pickling the object""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + + @typing.final + class Ellipsoid: + """Only the tri-axial Ellipsoid shape model is currently supported by ANISE. + This is directly inspired from SPICE PCK. + > For each body, three radii are listed: The first number is + > the largest equatorial radius (the length of the semi-axis + > containing the prime meridian), the second number is the smaller + > equatorial radius, and the third is the polar radius. + + Example: Radii of the Earth. + + BODY399_RADII = ( 6378.1366 6378.1366 6356.7519 )""" + polar_radius_km: float + semi_major_equatorial_radius_km: float + semi_minor_equatorial_radius_km: float + + def __init__(self, semi_major_equatorial_radius_km: float, polar_radius_km: float=None, semi_minor_equatorial_radius_km: float=None) -> Ellipsoid: + """Only the tri-axial Ellipsoid shape model is currently supported by ANISE. + This is directly inspired from SPICE PCK. + > For each body, three radii are listed: The first number is + > the largest equatorial radius (the length of the semi-axis + > containing the prime meridian), the second number is the smaller + > equatorial radius, and the third is the polar radius. + + Example: Radii of the Earth. + + BODY399_RADII = ( 6378.1366 6378.1366 6356.7519 )""" + + def flattening(self) -> float: + """Returns the flattening ratio, computed from the mean equatorial radius and the polar radius""" + + def is_sphere(self) -> bool: + """Returns true if the polar radius is equal to the semi minor radius.""" + + def is_spheroid(self) -> bool: + """Returns true if the semi major and minor radii are equal""" + + def mean_equatorial_radius_km(self) -> float: + """Returns the mean equatorial radius in kilometers""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self) -> typing.Tuple: + """Allows for pickling the object""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + + @typing.final + class Frame: + """A Frame uniquely defined by its ephemeris center and orientation. Refer to FrameDetail for frames combined with parameters.""" + ephemeris_id: int + orientation_id: int + shape: Ellipsoid + + def __init__(self, ephemeris_id: int, orientation_id: int, mu_km3_s2: float=None, shape: Ellipsoid=None) -> Frame: + """A Frame uniquely defined by its ephemeris center and orientation. Refer to FrameDetail for frames combined with parameters.""" + + def ephem_origin_id_match(self, other_id: int) -> bool: + """Returns true if the ephemeris origin is equal to the provided ID""" + + def ephem_origin_match(self, other: Frame) -> bool: + """Returns true if the ephemeris origin is equal to the provided frame""" + + def flattening(self) -> float: + """Returns the flattening ratio (unitless)""" + + def is_celestial(self) -> bool: + """Returns whether this is a celestial frame""" + + def is_geodetic(self) -> bool: + """Returns whether this is a geodetic frame""" + + def mean_equatorial_radius_km(self) -> float: + """Returns the mean equatorial radius in km, if defined""" + + def mu_km3_s2(self) -> float: + """Returns the gravitational parameters of this frame, if defined""" + + def orient_origin_id_match(self, other_id: int) -> bool: + """Returns true if the orientation origin is equal to the provided ID""" + + def orient_origin_match(self, other: Frame) -> bool: + """Returns true if the orientation origin is equal to the provided frame""" + + def polar_radius_km(self) -> float: + """Returns the polar radius in km, if defined""" + + def semi_major_radius_km(self) -> float: + """Returns the semi major radius of the tri-axial ellipoid shape of this frame, if defined""" + + def strip(self) -> None: + """Removes the graviational parameter and the shape information from this frame. + Use this to prevent astrodynamical computations.""" + + def with_ephem(self, new_ephem_id: int) -> Frame: + """Returns a copy of this Frame whose ephemeris ID is set to the provided ID""" + + def with_mu_km3_s2(self, mu_km3_s2: float) -> Frame: + """Returns a copy of this frame with the graviational parameter set to the new value.""" + + def with_orient(self, new_orient_id: int) -> Frame: + """Returns a copy of this Frame whose orientation ID is set to the provided ID""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self) -> typing.Tuple: + """Allows for pickling the object""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + + @typing.final + class Occultation: + """Stores the result of an occultation computation with the occulation percentage + Refer to the [MathSpec](https://nyxspace.com/nyxspace/MathSpec/celestial/eclipse/) for modeling details.""" + back_frame: Frame + epoch: Epoch + front_frame: Frame + percentage: float + + def factor(self) -> float: + """Returns the percentage as a factor between 0 and 1""" + + def is_eclipse_computation(self) -> bool: + """Returns true if the back object is the Sun, false otherwise""" + + def is_obstructed(self) -> bool: + """Returns true if the occultation percentage is greater than or equal 99.999%""" + + def is_partial(self) -> bool: + """Returns true if neither occulted nor visible (i.e. penumbra for solar eclipsing)""" + + def is_visible(self) -> bool: + """Returns true if the occultation percentage is less than or equal 0.001%""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + + @typing.final + class Orbit: + """Defines a Cartesian state in a given frame at a given epoch in a given time scale. Radius data is expressed in kilometers. Velocity data is expressed in kilometers per second. + Regardless of the constructor used, this struct stores all the state information in Cartesian coordinates as these are always non singular. + + Unless noted otherwise, algorithms are from GMAT 2016a [StateConversionUtil.cpp](https://github.com/ChristopherRabotin/GMAT/blob/37201a6290e7f7b941bc98ee973a527a5857104b/src/base/util/StateConversionUtil.cpp).""" + epoch: Epoch + frame: Frame + vx_km_s: float + vy_km_s: float + vz_km: None + vz_km_s: float + x_km: float + y_km: float + z_km: float + + def __init__(self, x_km: float, y_km: float, z_km: float, vx_km_s: float, vy_km_s: float, vz_km_s: float, epoch: Epoch, frame: Frame) -> Orbit: + """Defines a Cartesian state in a given frame at a given epoch in a given time scale. Radius data is expressed in kilometers. Velocity data is expressed in kilometers per second. + Regardless of the constructor used, this struct stores all the state information in Cartesian coordinates as these are always non singular. + + Unless noted otherwise, algorithms are from GMAT 2016a [StateConversionUtil.cpp](https://github.com/ChristopherRabotin/GMAT/blob/37201a6290e7f7b941bc98ee973a527a5857104b/src/base/util/StateConversionUtil.cpp).""" + + def abs_difference(self, other: Orbit) -> typing.Tuple: + """Returns the absolute position and velocity differences in km and km/s between this orbit and another. + Raises an error if the frames do not match (epochs do not need to match).""" + + def abs_pos_diff_km(self, other: Orbit) -> float: + """Returns the absolute position difference in kilometer between this orbit and another. + Raises an error if the frames do not match (epochs do not need to match).""" + + def abs_vel_diff_km_s(self, other: Orbit) -> float: + """Returns the absolute velocity difference in kilometer per second between this orbit and another. + Raises an error if the frames do not match (epochs do not need to match).""" + + def add_aop_deg(self, delta_aop_deg: float) -> Orbit: + """Returns a copy of the state with a provided AOP added to the current one""" + + def add_apoapsis_periapsis_km(self, delta_ra_km: float, delta_rp_km: float) -> Orbit: + """Returns a copy of this state with the provided apoasis and periapsis added to the current values""" + + def add_ecc(self, delta_ecc: float) -> Orbit: + """Returns a copy of the state with a provided ECC added to the current one""" + + def add_inc_deg(self, delta_inc_deg: float) -> None: + """Returns a copy of the state with a provided INC added to the current one""" + + def add_raan_deg(self, delta_raan_deg: float) -> Orbit: + """Returns a copy of the state with a provided RAAN added to the current one""" + + def add_sma_km(self, delta_sma_km: float) -> Orbit: + """Returns a copy of the state with a provided SMA added to the current one""" + + def add_ta_deg(self, delta_ta_deg: float) -> Orbit: + """Returns a copy of the state with a provided TA added to the current one""" + + def aol_deg(self) -> float: + """Returns the argument of latitude in degrees + + NOTE: If the orbit is near circular, the AoL will be computed from the true longitude + instead of relying on the ill-defined true anomaly.""" + + def aop_deg(self) -> float: + """Returns the argument of periapsis in degrees""" + + def apoapsis_altitude_km(self) -> float: + """Returns the altitude of apoapsis (or apogee around Earth), in kilometers.""" + + def apoapsis_km(self) -> float: + """Returns the radius of apoapsis (or apogee around Earth), in kilometers.""" + + def at_epoch(self, new_epoch: Epoch) -> Orbit: + """Adjusts the true anomaly of this orbit using the mean anomaly. + + # Astrodynamics note + This is not a true propagation of the orbit. This is akin to a two body propagation ONLY without any other force models applied. + Use Nyx for high fidelity propagation.""" + + def c3_km2_s2(self) -> float: + """Returns the $C_3$ of this orbit in km^2/s^2""" + + def declination_deg(self) -> float: + """Returns the declination of this orbit in degrees""" + + def distance_to_km(self, other: Orbit) -> float: + """Returns the distance in kilometers between this state and another state, if both frame match (epoch does not need to match).""" + + def ea_deg(self) -> float: + """Returns the eccentric anomaly in degrees + + This is a conversion from GMAT's StateConversionUtil::TrueToEccentricAnomaly""" + + def ecc(self) -> float: + """Returns the eccentricity (no unit)""" + + def energy_km2_s2(self) -> float: + """Returns the specific mechanical energy in km^2/s^2""" + + def eq_within(self, other: Orbit, radial_tol_km: float, velocity_tol_km_s: float) -> bool: + """Returns whether this orbit and another are equal within the specified radial and velocity absolute tolerances""" + + def fpa_deg(self) -> float: + """Returns the flight path angle in degrees""" + + @staticmethod + def from_cartesian(x_km: float, y_km: float, z_km: float, vx_km_s: float, vy_km_s: float, vz_km_s: float, epoch: Epoch, frame: Frame) -> Orbit: + """Creates a new Cartesian state in the provided frame at the provided Epoch. + + **Units:** km, km, km, km/s, km/s, km/s""" + + @staticmethod + def from_keplerian(sma_km: float, ecc: float, inc_deg: float, raan_deg: float, aop_deg: float, ta_deg: float, epoch: Epoch, frame: Frame) -> Orbit: + """Creates a new Orbit around the provided Celestial or Geoid frame from the Keplerian orbital elements. + + **Units:** km, none, degrees, degrees, degrees, degrees + + NOTE: The state is defined in Cartesian coordinates as they are non-singular. This causes rounding + errors when creating a state from its Keplerian orbital elements (cf. the state tests). + One should expect these errors to be on the order of 1e-12.""" + + @staticmethod + def from_keplerian_altitude(sma_altitude_km: float, ecc: float, inc_deg: float, raan_deg: float, aop_deg: float, ta_deg: float, epoch: Epoch, frame: Frame) -> Orbit: + """Creates a new Orbit from the provided semi-major axis altitude in kilometers""" + + @staticmethod + def from_keplerian_apsis_altitude(apo_alt_km: float, peri_alt_km: float, inc_deg: float, raan_deg: float, aop_deg: float, ta_deg: float, epoch: Epoch, frame: Frame) -> Orbit: + """Creates a new Orbit from the provided altitudes of apoapsis and periapsis, in kilometers""" + + @staticmethod + def from_keplerian_apsis_radii(r_a_km: float, r_p_km: float, inc_deg: float, raan_deg: float, aop_deg: float, ta_deg: float, epoch: Epoch, frame: Frame) -> Orbit: + """Attempts to create a new Orbit from the provided radii of apoapsis and periapsis, in kilometers""" + + @staticmethod + def from_keplerian_mean_anomaly(sma_km: float, ecc: float, inc_deg: float, raan_deg: float, aop_deg: float, ma_deg: float, epoch: Epoch, frame: Frame) -> Orbit: + """Initializes a new orbit from the Keplerian orbital elements using the mean anomaly instead of the true anomaly. + + # Implementation notes + This function starts by converting the mean anomaly to true anomaly, and then it initializes the orbit + using the keplerian(..) method. + The conversion is from GMAT's MeanToTrueAnomaly function, transliterated originally by Claude and GPT4 with human adjustments.""" + + @staticmethod + def from_latlongalt(latitude_deg: float, longitude_deg: float, height_km: float, angular_velocity: float, epoch: Epoch, frame: Frame) -> Orbit: + """Creates a new Orbit from the latitude (φ), longitude (λ) and height (in km) with respect to the frame's ellipsoid given the angular velocity. + + **Units:** degrees, degrees, km, rad/s + NOTE: This computation differs from the spherical coordinates because we consider the flattening of body. + Reference: G. Xu and Y. Xu, "GPS", DOI 10.1007/978-3-662-50367-6_2, 2016""" + + def height_km(self) -> float: + """Returns the geodetic height in km. + + Reference: Vallado, 4th Ed., Algorithm 12 page 172.""" + + def hmag(self) -> float: + """Returns the norm of the orbital momentum""" + + def hx(self) -> float: + """Returns the orbital momentum value on the X axis""" + + def hy(self) -> float: + """Returns the orbital momentum value on the Y axis""" + + def hyperbolic_anomaly_deg(self) -> float: + """Returns the hyperbolic anomaly in degrees between 0 and 360.0 + Returns an error if the orbit is not hyperbolic.""" + + def hz(self) -> float: + """Returns the orbital momentum value on the Z axis""" + + def inc_deg(self) -> float: + """Returns the inclination in degrees""" + + def is_brouwer_short_valid(self) -> bool: + """Returns whether this state satisfies the requirement to compute the Mean Brouwer Short orbital + element set. + + This is a conversion from GMAT's StateConversionUtil::CartesianToBrouwerMeanShort. + The details are at the log level `info`. + NOTE: Mean Brouwer Short are only defined around Earth. However, `nyx` does *not* check the + main celestial body around which the state is defined (GMAT does perform this verification).""" + + def latitude_deg(self) -> float: + """Returns the geodetic latitude (φ) in degrees. Value is between -180 and +180 degrees. + + # Frame warning + This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**.""" + + def latlongalt(self) -> typing.Tuple: + """Returns the geodetic latitude, geodetic longitude, and geodetic height, respectively in degrees, degrees, and kilometers. + + # Algorithm + This uses the Heikkinen procedure, which is not iterative. The results match Vallado and GMAT.""" + + def light_time(self) -> Duration: + """Returns the light time duration between this object and the origin of its reference frame.""" + + def longitude_360_deg(self) -> float: + """Returns the geodetic longitude (λ) in degrees. Value is between 0 and 360 degrees. + + # Frame warning + This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**.""" + + def longitude_deg(self) -> float: + """Returns the geodetic longitude (λ) in degrees. Value is between -180 and 180 degrees. + + # Frame warning + This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**.""" + + def ma_deg(self) -> float: + """Returns the mean anomaly in degrees + + This is a conversion from GMAT's StateConversionUtil::TrueToMeanAnomaly""" + + def periapsis_altitude_km(self) -> float: + """Returns the altitude of periapsis (or perigee around Earth), in kilometers.""" + + def periapsis_km(self) -> float: + """Returns the radius of periapsis (or perigee around Earth), in kilometers.""" + + def period(self) -> Duration: + """Returns the period in seconds""" + + def raan_deg(self) -> float: + """Returns the right ascension of the ascending node in degrees""" + + def rel_difference(self, other: Orbit) -> typing.Tuple: + """Returns the relative difference between this orbit and another for the position and velocity, respectively the first and second return values. + Both return values are UNITLESS because the relative difference is computed as the absolute difference divided by the rmag and vmag of this object. + Raises an error if the frames do not match, if the position is zero or the velocity is zero.""" + + def rel_pos_diff(self, other: Orbit) -> float: + """Returns the relative position difference (unitless) between this orbit and another. + This is computed by dividing the absolute difference by the norm of this object's radius vector. + If the radius is zero, this function raises a math error. + Raises an error if the frames do not match or (epochs do not need to match).""" + + def rel_vel_diff(self, other: Orbit) -> float: + """Returns the absolute velocity difference in kilometer per second between this orbit and another. + Raises an error if the frames do not match (epochs do not need to match).""" + + def ric_difference(self, other: Orbit) -> Orbit: + """Returns a Cartesian state representing the RIC difference between self and other, in position and velocity (with transport theorem). + Refer to dcm_from_ric_to_inertial for details on the RIC frame. + + # Algorithm + 1. Compute the RIC DCM of self + 2. Rotate self into the RIC frame + 3. Rotation other into the RIC frame + 4. Compute the difference between these two states + 5. Strip the astrodynamical information from the frame, enabling only computations from `CartesianState`""" + + def right_ascension_deg(self) -> float: + """Returns the right ascension of this orbit in degrees""" + + def rmag_km(self) -> float: + """Returns the magnitude of the radius vector in km""" + + def rms_radius_km(self, other: Orbit) -> float: + """Returns the root sum squared (RMS) radius difference between this state and another state, if both frames match (epoch does not need to match)""" + + def rms_velocity_km_s(self, other: Orbit) -> float: + """Returns the root sum squared (RMS) velocity difference between this state and another state, if both frames match (epoch does not need to match)""" + + def rss_radius_km(self, other: Orbit) -> float: + """Returns the root mean squared (RSS) radius difference between this state and another state, if both frames match (epoch does not need to match)""" + + def rss_velocity_km_s(self, other: Orbit) -> float: + """Returns the root mean squared (RSS) velocity difference between this state and another state, if both frames match (epoch does not need to match)""" + + def semi_minor_axis_km(self) -> float: + """Returns the semi minor axis in km, includes code for a hyperbolic orbit""" + + def semi_parameter_km(self) -> float: + """Returns the semi parameter (or semilatus rectum)""" + + def set_aop_deg(self, new_aop_deg: float) -> None: + """Mutates this orbit to change the AOP""" + + def set_ecc(self, new_ecc: float) -> None: + """Mutates this orbit to change the ECC""" + + def set_inc_deg(self, new_inc_deg: float) -> None: + """Mutates this orbit to change the INC""" + + def set_raan_deg(self, new_raan_deg: float) -> None: + """Mutates this orbit to change the RAAN""" + + def set_sma_km(self, new_sma_km: float) -> None: + """Mutates this orbit to change the SMA""" + + def set_ta_deg(self, new_ta_deg: float) -> None: + """Mutates this orbit to change the TA""" + + def sma_altitude_km(self) -> float: + """Returns the SMA altitude in km""" + + def sma_km(self) -> float: + """Returns the semi-major axis in km""" + + def ta_deg(self) -> float: + """Returns the true anomaly in degrees between 0 and 360.0 + + NOTE: This function will emit a warning stating that the TA should be avoided if in a very near circular orbit + Code from + + LIMITATION: For an orbit whose true anomaly is (very nearly) 0.0 or 180.0, this function may return either 0.0 or 180.0 with a very small time increment. + This is due to the precision of the cosine calculation: if the arccosine calculation is out of bounds, the sign of the cosine of the true anomaly is used + to determine whether the true anomaly should be 0.0 or 180.0. **In other words**, there is an ambiguity in the computation in the true anomaly exactly at 180.0 and 0.0.""" + + def ta_dot_deg_s(self) -> float: + """Returns the time derivative of the true anomaly computed as the 360.0 degrees divided by the orbital period (in seconds).""" + + def tlong_deg(self) -> float: + """Returns the true longitude in degrees""" + + def velocity_declination_deg(self) -> float: + """Returns the velocity declination of this orbit in degrees""" + + def vinf_periapsis_km(self, turn_angle_degrees: float) -> float: + """Returns the radius of periapse in kilometers for the provided turn angle of this hyperbolic orbit. + Returns an error if the orbit is not hyperbolic.""" + + def vinf_turn_angle_deg(self, periapsis_km: float) -> float: + """Returns the turn angle in degrees for the provided radius of periapse passage of this hyperbolic orbit + Returns an error if the orbit is not hyperbolic.""" + + def vmag_km_s(self) -> float: + """Returns the magnitude of the velocity vector in km/s""" + + def vnc_difference(self, other: Orbit) -> Orbit: + """Returns a Cartesian state representing the VNC difference between self and other, in position and velocity (with transport theorem). + Refer to dcm_from_vnc_to_inertial for details on the VNC frame. + + # Algorithm + 1. Compute the VNC DCM of self + 2. Rotate self into the VNC frame + 3. Rotation other into the VNC frame + 4. Compute the difference between these two states + 5. Strip the astrodynamical information from the frame, enabling only computations from `CartesianState`""" + + def with_aop_deg(self, new_aop_deg: float) -> Orbit: + """Returns a copy of the state with a new AOP""" + + def with_apoapsis_periapsis_km(self, new_ra_km: float, new_rp_km: float) -> Orbit: + """Returns a copy of this state with the provided apoasis and periapsis""" + + def with_ecc(self, new_ecc: float) -> Orbit: + """Returns a copy of the state with a new ECC""" + + def with_inc_deg(self, new_inc_deg: float) -> Orbit: + """Returns a copy of the state with a new INC""" + + def with_raan_deg(self, new_raan_deg: float) -> Orbit: + """Returns a copy of the state with a new RAAN""" + + def with_sma_km(self, new_sma_km: float) -> Orbit: + """Returns a copy of the state with a new SMA""" + + def with_ta_deg(self, new_ta_deg: float) -> Orbit: + """Returns a copy of the state with a new TA""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self) -> typing.Tuple:... + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + + class constants: + @typing.final + class CelestialObjects: + EARTH: int = ... + EARTH_MOON_BARYCENTER: int = ... + JUPITER: int = ... + JUPITER_BARYCENTER: int = ... + MARS: int = ... + MARS_BARYCENTER: int = ... + MERCURY: int = ... + MOON: int = ... + NEPTUNE: int = ... + NEPTUNE_BARYCENTER: int = ... + PLUTO_BARYCENTER: int = ... + SATURN: int = ... + SATURN_BARYCENTER: int = ... + SOLAR_SYSTEM_BARYCENTER: int = ... + SUN: int = ... + URANUS: int = ... + URANUS_BARYCENTER: int = ... + VENUS: int = ... + + @typing.final + class Frames: + EARTH_ECLIPJ2000: Frame = ... + EARTH_ITRF93: Frame = ... + EARTH_J2000: Frame = ... + EARTH_MOON_BARYCENTER_J2000: Frame = ... + EME2000: Frame = ... + IAU_EARTH_FRAME: Frame = ... + IAU_JUPITER_FRAME: Frame = ... + IAU_MARS_FRAME: Frame = ... + IAU_MERCURY_FRAME: Frame = ... + IAU_MOON_FRAME: Frame = ... + IAU_NEPTUNE_FRAME: Frame = ... + IAU_SATURN_FRAME: Frame = ... + IAU_URANUS_FRAME: Frame = ... + IAU_VENUS_FRAME: Frame = ... + JUPITER_BARYCENTER_J2000: Frame = ... + MARS_BARYCENTER_J2000: Frame = ... + MERCURY_J2000: Frame = ... + MOON_J2000: Frame = ... + MOON_ME_FRAME: Frame = ... + MOON_PA_FRAME: Frame = ... + NEPTUNE_BARYCENTER_J2000: Frame = ... + PLUTO_BARYCENTER_J2000: Frame = ... + SATURN_BARYCENTER_J2000: Frame = ... + SSB_J2000: Frame = ... + SUN_J2000: Frame = ... + URANUS_BARYCENTER_J2000: Frame = ... + VENUS_J2000: Frame = ... + + @typing.final + class Orientations: + ECLIPJ2000: int = ... + IAU_EARTH: int = ... + IAU_JUPITER: int = ... + IAU_MARS: int = ... + IAU_MERCURY: int = ... + IAU_MOON: int = ... + IAU_NEPTUNE: int = ... + IAU_SATURN: int = ... + IAU_URANUS: int = ... + IAU_VENUS: int = ... + ITRF93: int = ... + J2000: int = ... + MOON_ME: int = ... + MOON_PA: int = ... + + @typing.final + class UsualConstants: + MEAN_EARTH_ANGULAR_VELOCITY_DEG_S: float = ... + MEAN_MOON_ANGULAR_VELOCITY_DEG_S: float = ... + SPEED_OF_LIGHT_KM_S: float = ... @typing.final class time: - Duration: type = ... - Epoch: type = ... - LatestLeapSeconds: type = ... - LeapSecondsFile: type = ... - TimeScale: type = ... - TimeSeries: type = ... - Unit: type = ... - Ut1Provider: type = ... - __all__: list = ... - __name__: str = ... + @typing.final + class Duration: + """Defines generally usable durations for nanosecond precision valid for 32,768 centuries in either direction, and only on 80 bits / 10 octets. + + **Important conventions:** + 1. The negative durations can be mentally modeled "BC" years. One hours before 01 Jan 0000, it was "-1" years but 365 days and 23h into the current day. + It was decided that the nanoseconds corresponds to the nanoseconds _into_ the current century. In other words, + a duration with centuries = -1 and nanoseconds = 0 is _a greater duration_ (further from zero) than centuries = -1 and nanoseconds = 1. + Duration zero minus one nanosecond returns a century of -1 and a nanosecond set to the number of nanoseconds in one century minus one. + That difference is exactly 1 nanoseconds, where the former duration is "closer to zero" than the latter. + As such, the largest negative duration that can be represented sets the centuries to i16::MAX and its nanoseconds to NANOSECONDS_PER_CENTURY. + 2. It was also decided that opposite durations are equal, e.g. -15 minutes == 15 minutes. If the direction of time matters, use the signum function. + + (Python documentation hints)""" + + def __init__(self, string_repr: str) -> Duration: + """Defines generally usable durations for nanosecond precision valid for 32,768 centuries in either direction, and only on 80 bits / 10 octets. + + **Important conventions:** + 1. The negative durations can be mentally modeled "BC" years. One hours before 01 Jan 0000, it was "-1" years but 365 days and 23h into the current day. + It was decided that the nanoseconds corresponds to the nanoseconds _into_ the current century. In other words, + a duration with centuries = -1 and nanoseconds = 0 is _a greater duration_ (further from zero) than centuries = -1 and nanoseconds = 1. + Duration zero minus one nanosecond returns a century of -1 and a nanosecond set to the number of nanoseconds in one century minus one. + That difference is exactly 1 nanoseconds, where the former duration is "closer to zero" than the latter. + As such, the largest negative duration that can be represented sets the centuries to i16::MAX and its nanoseconds to NANOSECONDS_PER_CENTURY. + 2. It was also decided that opposite durations are equal, e.g. -15 minutes == 15 minutes. If the direction of time matters, use the signum function. + + (Python documentation hints)""" + + @staticmethod + def EPSILON():... + + @staticmethod + def MAX():... + + @staticmethod + def MIN():... + + @staticmethod + def MIN_NEGATIVE():... + + @staticmethod + def MIN_POSITIVE():... + + @staticmethod + def ZERO():... + + def abs(self) -> Duration: + """Returns the absolute value of this duration""" + + def approx(self) -> Duration: + """Rounds this duration to the largest units represented in this duration. + + This is useful to provide an approximate human duration. Under the hood, this function uses `round`, + so the "tipping point" of the rounding is half way to the next increment of the greatest unit. + As shown below, one example is that 35 hours and 59 minutes rounds to 1 day, but 36 hours and 1 minute rounds + to 2 days because 2 days is closer to 36h 1 min than 36h 1 min is to 1 day. + + # Example + + ``` + use hifitime::{Duration, TimeUnits}; + + assert_eq!((2.hours() + 3.minutes()).approx(), 2.hours()); + assert_eq!((24.hours() + 3.minutes()).approx(), 1.days()); + assert_eq!((35.hours() + 59.minutes()).approx(), 1.days()); + assert_eq!((36.hours() + 1.minutes()).approx(), 2.days()); + assert_eq!((47.hours() + 3.minutes()).approx(), 2.days()); + assert_eq!((49.hours() + 3.minutes()).approx(), 2.days()); + ```""" + + def ceil(self, duration: Duration) -> Duration: + """Ceils this duration to the closest provided duration + + This simply floors then adds the requested duration + + # Example + ``` + use hifitime::{Duration, TimeUnits}; + + let two_hours_three_min = 2.hours() + 3.minutes(); + assert_eq!(two_hours_three_min.ceil(1.hours()), 3.hours()); + assert_eq!(two_hours_three_min.ceil(30.minutes()), 2.hours() + 30.minutes()); + assert_eq!(two_hours_three_min.ceil(4.hours()), 4.hours()); + assert_eq!(two_hours_three_min.ceil(1.seconds()), two_hours_three_min + 1.seconds()); + assert_eq!(two_hours_three_min.ceil(1.hours() + 5.minutes()), 2.hours() + 10.minutes()); + ```""" + + def decompose(self) -> typing.Tuple: + """Decomposes a Duration in its sign, days, hours, minutes, seconds, ms, us, ns""" + + def floor(self, duration: Duration) -> Duration: + """Floors this duration to the closest duration from the bottom + + # Example + ``` + use hifitime::{Duration, TimeUnits}; + + let two_hours_three_min = 2.hours() + 3.minutes(); + assert_eq!(two_hours_three_min.floor(1.hours()), 2.hours()); + assert_eq!(two_hours_three_min.floor(30.minutes()), 2.hours()); + // This is zero because we floor by a duration longer than the current duration, rounding it down + assert_eq!(two_hours_three_min.floor(4.hours()), 0.hours()); + assert_eq!(two_hours_three_min.floor(1.seconds()), two_hours_three_min); + assert_eq!(two_hours_three_min.floor(1.hours() + 1.minutes()), 2.hours() + 2.minutes()); + assert_eq!(two_hours_three_min.floor(1.hours() + 5.minutes()), 1.hours() + 5.minutes()); + ```""" + + @staticmethod + def from_all_parts(sign: int, days: int, hours: int, minutes: int, seconds: int, milliseconds: int, microseconds: int, nanoseconds: int) -> Duration: + """Creates a new duration from its parts""" + + @staticmethod + def from_parts(centuries: int, nanoseconds: int) -> Duration: + """Create a normalized duration from its parts""" + + @staticmethod + def from_total_nanoseconds(nanos: int) -> Duration: + """Creates a new Duration from its full nanoseconds""" + + def is_negative(self) -> bool: + """Returns whether this is a negative or positive duration.""" + + def max(self, other: Duration) -> Duration: + """Returns the maximum of the two durations. + + ``` + use hifitime::TimeUnits; + + let d0 = 20.seconds(); + let d1 = 21.seconds(); + + assert_eq!(d1, d1.max(d0)); + assert_eq!(d1, d0.max(d1)); + ```""" + + def min(self, other: Duration) -> Duration: + """Returns the minimum of the two durations. + + ``` + use hifitime::TimeUnits; + + let d0 = 20.seconds(); + let d1 = 21.seconds(); + + assert_eq!(d0, d1.min(d0)); + assert_eq!(d0, d0.min(d1)); + ```""" + + def round(self, duration: Duration) -> Duration: + """Rounds this duration to the closest provided duration + + This performs both a `ceil` and `floor` and returns the value which is the closest to current one. + # Example + ``` + use hifitime::{Duration, TimeUnits}; + + let two_hours_three_min = 2.hours() + 3.minutes(); + assert_eq!(two_hours_three_min.round(1.hours()), 2.hours()); + assert_eq!(two_hours_three_min.round(30.minutes()), 2.hours()); + assert_eq!(two_hours_three_min.round(4.hours()), 4.hours()); + assert_eq!(two_hours_three_min.round(1.seconds()), two_hours_three_min); + assert_eq!(two_hours_three_min.round(1.hours() + 5.minutes()), 2.hours() + 10.minutes()); + ```""" + + def signum(self) -> int: + """Returns the sign of this duration + + 0 if the number is zero + + 1 if the number is positive + + -1 if the number is negative""" + + def to_parts(self) -> typing.Tuple: + """Returns the centuries and nanoseconds of this duration + NOTE: These items are not public to prevent incorrect durations from being created by modifying the values of the structure directly.""" + + def to_seconds(self) -> float: + """Returns this duration in seconds f64. + For high fidelity comparisons, it is recommended to keep using the Duration structure.""" + + def to_unit(self, unit: Unit) -> float:... + + def total_nanoseconds(self) -> int: + """Returns the total nanoseconds in a signed 128 bit integer""" + + def __add__(): + """Return self+value.""" + + def __div__():... + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self):... + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __radd__(): + """Return value+self.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __rmul__(): + """Return value*self.""" + + def __rsub__(): + """Return value-self.""" + + def __str__(self) -> str: + """Return str(self).""" + + def __sub__(): + """Return self-value.""" + + @typing.final + class DurationError: + __cause__: typing.Any + __context__: typing.Any + __suppress_context__: typing.Any + __traceback__: typing.Any + args: typing.Any + + def add_note(): + """Exception.add_note(note) -- + add a note to the exception""" + + def with_traceback(): + """Exception.with_traceback(tb) -- + set self.__traceback__ to tb and return self.""" + + def __delattr__(): + """Implement delattr(self, name).""" + + def __getattribute__(): + """Return getattr(self, name).""" + + def __init__(): + """Initialize self. See help(type(self)) for accurate signature.""" + + def __repr__(): + """Return repr(self).""" + + def __setattr__(): + """Implement setattr(self, name, value).""" + + def __setstate__():... + + def __str__(): + """Return str(self).""" + + @typing.final + class Epoch: + """Defines a nanosecond-precision Epoch. + + Refer to the appropriate functions for initializing this Epoch from different time scales or representations. + + (Python documentation hints)""" + + def __init__(self, string_repr: str) -> Epoch: + """Defines a nanosecond-precision Epoch. + + Refer to the appropriate functions for initializing this Epoch from different time scales or representations. + + (Python documentation hints)""" + + def day_of_year(self) -> float: + """Returns the number of days since the start of the year.""" + + def duration_in_year(self) -> Duration: + """Returns the duration since the start of the year""" + + def hours(self) -> int: + """Returns the hours of the Gregorian representation of this epoch in the time scale it was initialized in.""" + + @staticmethod + def init_from_bdt_days(days: float) -> Epoch: + """Initialize an Epoch from the number of days since the BeiDou Time Epoch, + defined as January 1st 2006 (cf. ).""" + + @staticmethod + def init_from_bdt_nanoseconds(nanoseconds: float) -> Epoch: + """Initialize an Epoch from the number of days since the BeiDou Time Epoch, + defined as January 1st 2006 (cf. ). + This may be useful for time keeping devices that use BDT as a time source.""" + + @staticmethod + def init_from_bdt_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the number of seconds since the BeiDou Time Epoch, + defined as January 1st 2006 (cf. ).""" + + @staticmethod + def init_from_et_duration(duration_since_j2000: Duration) -> Epoch: + """Initialize an Epoch from the Ephemeris Time duration past 2000 JAN 01 (J2000 reference)""" + + @staticmethod + def init_from_et_seconds(seconds_since_j2000: float) -> Epoch: + """Initialize an Epoch from the Ephemeris Time seconds past 2000 JAN 01 (J2000 reference)""" + + @staticmethod + def init_from_gpst_days(days: float) -> Epoch: + """Initialize an Epoch from the number of days since the GPS Time Epoch, + defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + @staticmethod + def init_from_gpst_nanoseconds(nanoseconds: float) -> Epoch: + """Initialize an Epoch from the number of nanoseconds since the GPS Time Epoch, + defined as UTC midnight of January 5th to 6th 1980 (cf. ). + This may be useful for time keeping devices that use GPS as a time source.""" + + @staticmethod + def init_from_gpst_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the number of seconds since the GPS Time Epoch, + defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + @staticmethod + def init_from_gregorian(year: int, month: int, day: int, hour: int, minute: int, second: int, nanos: int, time_scale: TimeScale) -> Epoch: + """Initialize from the Gregorian parts""" + + @staticmethod + def init_from_gregorian_at_midnight(year: int, month: int, day: int, time_scale: TimeScale) -> Epoch: + """Initialize from the Gregorian parts, time set to midnight""" + + @staticmethod + def init_from_gregorian_at_noon(year: int, month: int, day: int, time_scale: TimeScale) -> Epoch: + """Initialize from the Gregorian parts, time set to noon""" + + @staticmethod + def init_from_gregorian_utc(year: int, month: int, day: int, hour: int, minute: int, second: int, nanos: int) -> Epoch: + """Builds an Epoch from the provided Gregorian date and time in TAI. If invalid date is provided, this function will panic. + Use maybe_from_gregorian_tai if unsure.""" + + @staticmethod + def init_from_gst_days(days: float) -> Epoch: + """Initialize an Epoch from the number of days since the Galileo Time Epoch, + starting on August 21st 1999 Midnight UT, + (cf. ).""" + + @staticmethod + def init_from_gst_nanoseconds(nanoseconds: float) -> Epoch: + """Initialize an Epoch from the number of nanoseconds since the Galileo Time Epoch, + starting on August 21st 1999 Midnight UT, + (cf. ). + This may be useful for time keeping devices that use GST as a time source.""" + + @staticmethod + def init_from_gst_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the number of seconds since the Galileo Time Epoch, + starting on August 21st 1999 Midnight UT, + (cf. ).""" + + @staticmethod + def init_from_jde_et(days: float) -> Epoch: + """Initialize from the JDE days""" + + @staticmethod + def init_from_jde_tai(days: float) -> Epoch: + """Initialize an Epoch from given JDE in TAI time scale""" + + @staticmethod + def init_from_jde_tdb(days: float) -> Epoch: + """Initialize from Dynamic Barycentric Time (TDB) (same as SPICE ephemeris time) in JD days""" + + @staticmethod + def init_from_jde_utc(days: float) -> Epoch: + """Initialize an Epoch from given JDE in UTC time scale""" + + @staticmethod + def init_from_mjd_tai(days: float) -> Epoch: + """Initialize an Epoch from given MJD in TAI time scale""" + + @staticmethod + def init_from_mjd_utc(days: float) -> Epoch: + """Initialize an Epoch from given MJD in UTC time scale""" + + @staticmethod + def init_from_qzsst_days(days: float) -> Epoch: + """Initialize an Epoch from the number of days since the QZSS Time Epoch, + defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + @staticmethod + def init_from_qzsst_nanoseconds(nanoseconds: int) -> Epoch: + """Initialize an Epoch from the number of nanoseconds since the QZSS Time Epoch, + defined as UTC midnight of January 5th to 6th 1980 (cf. ). + This may be useful for time keeping devices that use QZSS as a time source.""" + + @staticmethod + def init_from_qzsst_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the number of seconds since the QZSS Time Epoch, + defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + @staticmethod + def init_from_tai_days(days: float) -> Epoch: + """Initialize an Epoch from the provided TAI days since 1900 January 01 at midnight""" + + @staticmethod + def init_from_tai_duration(duration: Duration) -> Epoch: + """Creates a new Epoch from a Duration as the time difference between this epoch and TAI reference epoch.""" + + @staticmethod + def init_from_tai_parts(centuries: int, nanoseconds: int) -> Epoch: + """Creates a new Epoch from its centuries and nanosecond since the TAI reference epoch.""" + + @staticmethod + def init_from_tai_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the provided TAI seconds since 1900 January 01 at midnight""" + + @staticmethod + def init_from_tdb_duration(duration_since_j2000: Duration) -> Epoch: + """Initialize from Dynamic Barycentric Time (TDB) (same as SPICE ephemeris time) whose epoch is 2000 JAN 01 noon TAI.""" + + @staticmethod + def init_from_tdb_seconds(seconds_j2000: float) -> Epoch: + """Initialize an Epoch from Dynamic Barycentric Time (TDB) seconds past 2000 JAN 01 midnight (difference than SPICE) + NOTE: This uses the ESA algorithm, which is a notch more complicated than the SPICE algorithm, but more precise. + In fact, SPICE algorithm is precise +/- 30 microseconds for a century whereas ESA algorithm should be exactly correct.""" + + @staticmethod + def init_from_tt_duration(duration: Duration) -> Epoch: + """Initialize an Epoch from the provided TT seconds (approximated to 32.184s delta from TAI)""" + + @staticmethod + def init_from_tt_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the provided TT seconds (approximated to 32.184s delta from TAI)""" + + @staticmethod + def init_from_unix_milliseconds(milliseconds: float) -> Epoch: + """Initialize an Epoch from the provided UNIX millisecond timestamp since UTC midnight 1970 January 01.""" + + @staticmethod + def init_from_unix_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the provided UNIX second timestamp since UTC midnight 1970 January 01.""" + + @staticmethod + def init_from_utc_days(days: float) -> Epoch: + """Initialize an Epoch from the provided UTC days since 1900 January 01 at midnight""" + + @staticmethod + def init_from_utc_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the provided UTC seconds since 1900 January 01 at midnight""" + + def isoformat(self) -> str: + """Equivalent to `datetime.isoformat`, and truncated to 23 chars, refer to for format options""" + + def leap_seconds(self, iers_only: bool) -> float: + """Get the accumulated number of leap seconds up to this Epoch accounting only for the IERS leap seconds and the SOFA scaling from 1960 to 1972, depending on flag. + Returns None if the epoch is before 1960, year at which UTC was defined. + + # Why does this function return an `Option` when the other returns a value + This is to match the `iauDat` function of SOFA (src/dat.c). That function will return a warning and give up if the start date is before 1960.""" + + def leap_seconds_iers(self) -> int: + """Get the accumulated number of leap seconds up to this Epoch accounting only for the IERS leap seconds.""" + + def leap_seconds_with_file(self, iers_only: bool, provider: LeapSecondsFile) -> float: + """Get the accumulated number of leap seconds up to this Epoch from the provided LeapSecondProvider. + Returns None if the epoch is before 1960, year at which UTC was defined. + + # Why does this function return an `Option` when the other returns a value + This is to match the `iauDat` function of SOFA (src/dat.c). That function will return a warning and give up if the start date is before 1960.""" + + def microseconds(self) -> int: + """Returns the microseconds of the Gregorian representation of this epoch in the time scale it was initialized in.""" + + def milliseconds(self) -> int: + """Returns the milliseconds of the Gregorian representation of this epoch in the time scale it was initialized in.""" + + def minutes(self) -> int: + """Returns the minutes of the Gregorian representation of this epoch in the time scale it was initialized in.""" + + def month_name(self) -> MonthName:... + + def nanoseconds(self) -> int: + """Returns the nanoseconds of the Gregorian representation of this epoch in the time scale it was initialized in.""" + + def seconds(self) -> int: + """Returns the seconds of the Gregorian representation of this epoch in the time scale it was initialized in.""" + + def strftime(self, format_str: str) -> str: + """Equivalent to `datetime.strftime`, refer to for format options""" + + @staticmethod + def strptime(epoch_str: str, format_str: str) -> Epoch: + """Equivalent to `datetime.strptime`, refer to for format options""" + + @staticmethod + def system_now() -> Epoch: + """Returns the computer clock in UTC""" + + def timedelta(self, other: Duration) -> Duration: + """Differences between two epochs""" + + def to_bdt_days(self) -> float: + """Returns days past BDT (BeiDou) Time Epoch, defined as Jan 01 2006 UTC + (cf. ).""" + + def to_bdt_duration(self) -> Duration: + """Returns `Duration` past BDT (BeiDou) time Epoch.""" + + def to_bdt_nanoseconds(self) -> int: + """Returns nanoseconds past BDT (BeiDou) Time Epoch, defined as Jan 01 2006 UTC + (cf. ). + NOTE: This function will return an error if the centuries past GST time are not zero.""" + + def to_bdt_seconds(self) -> float: + """Returns seconds past BDT (BeiDou) Time Epoch""" + + def to_duration_in_time_scale(self, ts: TimeScale) -> Duration: + """Returns this epoch with respect to the provided time scale. + This is needed to correctly perform duration conversions in dynamical time scales (e.g. TDB).""" + + def to_et_centuries_since_j2000(self) -> float: + """Returns the number of centuries since Ephemeris Time (ET) J2000 (used for Archinal et al. rotations)""" + + def to_et_days_since_j2000(self) -> float: + """Returns the number of days since Ephemeris Time (ET) J2000 (used for Archinal et al. rotations)""" + + def to_et_duration(self) -> Duration: + """Returns the duration between J2000 and the current epoch as per NAIF SPICE. + + # Warning + The et2utc function of NAIF SPICE will assume that there are 9 leap seconds before 01 JAN 1972, + as this date introduces 10 leap seconds. At the time of writing, this does _not_ seem to be in + line with IERS and the documentation in the leap seconds list. + + In order to match SPICE, the as_et_duration() function will manually get rid of that difference.""" + + def to_et_seconds(self) -> float: + """Returns the Ephemeris Time seconds past 2000 JAN 01 midnight, matches NASA/NAIF SPICE.""" + + def to_gpst_days(self) -> float: + """Returns days past GPS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + def to_gpst_duration(self) -> Duration: + """Returns `Duration` past GPS time Epoch.""" + + def to_gpst_nanoseconds(self) -> int: + """Returns nanoseconds past GPS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ). + NOTE: This function will return an error if the centuries past GPST time are not zero.""" + + def to_gpst_seconds(self) -> float: + """Returns seconds past GPS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + def to_gst_days(self) -> float: + """Returns days past GST (Galileo) Time Epoch, + starting on August 21st 1999 Midnight UT + (cf. ).""" + + def to_gst_duration(self) -> Duration: + """Returns `Duration` past GST (Galileo) time Epoch.""" + + def to_gst_nanoseconds(self) -> int: + """Returns nanoseconds past GST (Galileo) Time Epoch, starting on August 21st 1999 Midnight UT + (cf. ). + NOTE: This function will return an error if the centuries past GST time are not zero.""" + + def to_gst_seconds(self) -> float: + """Returns seconds past GST (Galileo) Time Epoch""" + + def to_isoformat(self) -> str: + """The standard ISO format of this epoch (six digits of subseconds) in the _current_ time scale, refer to for format options.""" + + def to_jde_et(self, unit: Unit) -> float:... + + def to_jde_et_days(self) -> float: + """Returns the Ephemeris Time JDE past epoch""" + + def to_jde_et_duration(self) -> Duration:... + + def to_jde_tai(self, unit: Unit) -> float: + """Returns the Julian Days from epoch 01 Jan -4713 12:00 (noon) in desired Duration::Unit""" + + def to_jde_tai_days(self) -> float: + """Returns the Julian days from epoch 01 Jan -4713, 12:00 (noon) + as explained in "Fundamentals of astrodynamics and applications", Vallado et al. + 4th edition, page 182, and on [Wikipedia](https://en.wikipedia.org/wiki/Julian_day).""" + + def to_jde_tai_duration(self) -> Duration: + """Returns the Julian Days from epoch 01 Jan -4713 12:00 (noon) as a Duration""" + + def to_jde_tai_seconds(self) -> float: + """Returns the Julian seconds in TAI.""" + + def to_jde_tdb_days(self) -> float: + """Returns the Dynamic Barycentric Time (TDB) (higher fidelity SPICE ephemeris time) whose epoch is 2000 JAN 01 noon TAI (cf. )""" + + def to_jde_tdb_duration(self) -> Duration:... + + def to_jde_tt_days(self) -> float: + """Returns days past Julian epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT))""" + + def to_jde_tt_duration(self) -> Duration:... + + def to_jde_utc_days(self) -> float: + """Returns the Julian days in UTC.""" + + def to_jde_utc_duration(self) -> Duration: + """Returns the Julian days in UTC as a `Duration`""" + + def to_jde_utc_seconds(self) -> float: + """Returns the Julian Days in UTC seconds.""" + + def to_mjd_tai(self, unit: Unit) -> float: + """Returns this epoch as a duration in the requested units in MJD TAI""" + + def to_mjd_tai_days(self) -> float: + """`as_mjd_days` creates an Epoch from the provided Modified Julian Date in days as explained + [here](http://tycho.usno.navy.mil/mjd.html). MJD epoch is Modified Julian Day at 17 November 1858 at midnight.""" + + def to_mjd_tai_seconds(self) -> float: + """Returns the Modified Julian Date in seconds TAI.""" + + def to_mjd_tt_days(self) -> float: + """Returns days past Modified Julian epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT))""" + + def to_mjd_tt_duration(self) -> Duration:... + + def to_mjd_utc(self, unit: Unit) -> float: + """Returns the Modified Julian Date in the provided unit in UTC.""" + + def to_mjd_utc_days(self) -> float: + """Returns the Modified Julian Date in days UTC.""" + + def to_mjd_utc_seconds(self) -> float: + """Returns the Modified Julian Date in seconds UTC.""" + + def to_nanoseconds_in_time_scale(self, time_scale: TimeScale) -> int: + """Attempts to return the number of nanoseconds since the reference epoch of the provided time scale. + This will return an overflow error if more than one century has past since the reference epoch in the provided time scale. + If this is _not_ an issue, you should use `epoch.to_duration_in_time_scale().to_parts()` to retrieve both the centuries and the nanoseconds + in that century.""" + + def to_qzsst_days(self) -> float: + """Returns days past QZSS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + def to_qzsst_duration(self) -> Duration: + """Returns `Duration` past QZSS time Epoch.""" + + def to_qzsst_nanoseconds(self) -> int: + """Returns nanoseconds past QZSS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ). + NOTE: This function will return an error if the centuries past QZSST time are not zero.""" + + def to_qzsst_seconds(self) -> float: + """Returns seconds past QZSS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + def to_rfc3339(self) -> str: + """Returns this epoch in UTC in the RFC3339 format""" + + def to_tai(self, unit: Unit) -> float: + """Returns the epoch as a floating point value in the provided unit""" + + def to_tai_days(self) -> float: + """Returns the number of days since J1900 in TAI""" + + def to_tai_duration(self) -> Duration: + """Returns this time in a Duration past J1900 counted in TAI""" + + def to_tai_parts(self) -> typing.Tuple: + """Returns the TAI parts of this duration""" + + def to_tai_seconds(self) -> float: + """Returns the number of TAI seconds since J1900""" + + def to_tdb_centuries_since_j2000(self) -> float: + """Returns the number of centuries since Dynamic Barycentric Time (TDB) J2000 (used for Archinal et al. rotations)""" + + def to_tdb_days_since_j2000(self) -> float: + """Returns the number of days since Dynamic Barycentric Time (TDB) J2000 (used for Archinal et al. rotations)""" + + def to_tdb_duration(self) -> Duration: + """Returns the Dynamics Barycentric Time (TDB) as a high precision Duration since J2000 + + ## Algorithm + Given the embedded sine functions in the equation to compute the difference between TDB and TAI from the number of TDB seconds + past J2000, one cannot solve the revert the operation analytically. Instead, we iterate until the value no longer changes. + + 1. Assume that the TAI duration is in fact the TDB seconds from J2000. + 2. Offset to J2000 because `Epoch` stores everything in the J1900 but the TDB duration is in J2000. + 3. Compute the offset `g` due to the TDB computation with the current value of the TDB seconds (defined in step 1). + 4. Subtract that offset to the latest TDB seconds and store this as a new candidate for the true TDB seconds value. + 5. Compute the difference between this candidate and the previous one. If the difference is less than one nanosecond, stop iteration. + 6. Set the new candidate as the TDB seconds since J2000 and loop until step 5 breaks the loop, or we've done five iterations. + 7. At this stage, we have a good approximation of the TDB seconds since J2000. + 8. Reverse the algorithm given that approximation: compute the `g` offset, compute the difference between TDB and TAI, add the TT offset (32.184 s), and offset by the difference between J1900 and J2000.""" + + def to_tdb_seconds(self) -> float: + """Returns the Dynamic Barycentric Time (TDB) (higher fidelity SPICE ephemeris time) whose epoch is 2000 JAN 01 noon TAI (cf. )""" + + def to_time_scale(self, ts: TimeScale) -> Epoch: + """Converts self to another time scale + + As per the [Rust naming convention](https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv), + this borrows an Epoch and returns an owned Epoch.""" + + def to_tt_centuries_j2k(self) -> float: + """Returns the centuries passed J2000 TT""" + + def to_tt_days(self) -> float: + """Returns days past TAI epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT))""" + + def to_tt_duration(self) -> Duration: + """Returns `Duration` past TAI epoch in Terrestrial Time (TT).""" + + def to_tt_seconds(self) -> float: + """Returns seconds past TAI epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT))""" + + def to_tt_since_j2k(self) -> Duration: + """Returns the duration past J2000 TT""" + + def to_unix(self, unit: Unit) -> float: + """Returns the duration since the UNIX epoch in the provided unit.""" + + def to_unix_days(self) -> float: + """Returns the number days since the UNIX epoch defined 01 Jan 1970 midnight UTC.""" + + def to_unix_duration(self) -> Duration: + """Returns the Duration since the UNIX epoch UTC midnight 01 Jan 1970.""" + + def to_unix_milliseconds(self) -> float: + """Returns the number milliseconds since the UNIX epoch defined 01 Jan 1970 midnight UTC.""" + + def to_unix_seconds(self) -> float: + """Returns the number seconds since the UNIX epoch defined 01 Jan 1970 midnight UTC.""" + + def to_utc(self, unit: Unit) -> float: + """Returns the number of UTC seconds since the TAI epoch""" + + def to_utc_days(self) -> float: + """Returns the number of UTC days since the TAI epoch""" + + def to_utc_duration(self) -> Duration: + """Returns this time in a Duration past J1900 counted in UTC""" + + def to_utc_seconds(self) -> float: + """Returns the number of UTC seconds since the TAI epoch""" + + def year(self) -> int: + """Returns the number of Gregorian years of this epoch in the current time scale.""" + + def year_days_of_year(self) -> typing.Tuple: + """Returns the year and the days in the year so far (days of year).""" + + def __add__(): + """Return self+value.""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self):... + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __radd__(): + """Return value+self.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __rsub__(): + """Return value-self.""" + + def __str__(self) -> str: + """Return str(self).""" + + def __sub__(): + """Return self-value.""" + + @typing.final + class HifitimeError: + __cause__: typing.Any + __context__: typing.Any + __suppress_context__: typing.Any + __traceback__: typing.Any + args: typing.Any + + def add_note(): + """Exception.add_note(note) -- + add a note to the exception""" + + def with_traceback(): + """Exception.with_traceback(tb) -- + set self.__traceback__ to tb and return self.""" + + def __delattr__(): + """Implement delattr(self, name).""" + + def __getattribute__(): + """Return getattr(self, name).""" + + def __init__(): + """Initialize self. See help(type(self)) for accurate signature.""" + + def __repr__(): + """Return repr(self).""" + + def __setattr__(): + """Implement setattr(self, name, value).""" + + def __setstate__():... + + def __str__(): + """Return str(self).""" + + @typing.final + class LatestLeapSeconds: + """List of leap seconds from https://www.ietf.org/timezones/data/leap-seconds.list . + This list corresponds the number of seconds in TAI to the UTC offset and to whether it was an announced leap second or not. + The unannoucned leap seconds come from dat.c in the SOFA library.""" + + def __init__(self) -> None: + """List of leap seconds from https://www.ietf.org/timezones/data/leap-seconds.list . + This list corresponds the number of seconds in TAI to the UTC offset and to whether it was an announced leap second or not. + The unannoucned leap seconds come from dat.c in the SOFA library.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + @typing.final + class LeapSecondsFile: + """A leap second provider that uses an IERS formatted leap seconds file. + + (Python documentation hints)""" + + def __init__(self, path: str) -> LeapSecondsFile: + """A leap second provider that uses an IERS formatted leap seconds file. + + (Python documentation hints)""" + + def __repr__(self) -> str: + """Return repr(self).""" + + @typing.final + class MonthName: + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __int__(self) -> None: + """int(self)""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + April: MonthName = ... + August: MonthName = ... + December: MonthName = ... + February: MonthName = ... + January: MonthName = ... + July: MonthName = ... + June: MonthName = ... + March: MonthName = ... + May: MonthName = ... + November: MonthName = ... + October: MonthName = ... + September: MonthName = ... + + @typing.final + class ParsingError: + __cause__: typing.Any + __context__: typing.Any + __suppress_context__: typing.Any + __traceback__: typing.Any + args: typing.Any + + def add_note(): + """Exception.add_note(note) -- + add a note to the exception""" + + def with_traceback(): + """Exception.with_traceback(tb) -- + set self.__traceback__ to tb and return self.""" + + def __delattr__(): + """Implement delattr(self, name).""" + + def __getattribute__(): + """Return getattr(self, name).""" + + def __init__(): + """Initialize self. See help(type(self)) for accurate signature.""" + + def __repr__(): + """Return repr(self).""" + + def __setattr__(): + """Implement setattr(self, name, value).""" + + def __setstate__():... + + def __str__(): + """Return str(self).""" + + @typing.final + class TimeScale: + """Enum of the different time systems available""" + + def uses_leap_seconds(self) -> bool: + """Returns true if self takes leap seconds into account""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __int__(self) -> None: + """int(self)""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + BDT: TimeScale = ... + ET: TimeScale = ... + GPST: TimeScale = ... + GST: TimeScale = ... + QZSST: TimeScale = ... + TAI: TimeScale = ... + TDB: TimeScale = ... + TT: TimeScale = ... + UTC: TimeScale = ... + + @typing.final + class TimeSeries: + """An iterator of a sequence of evenly spaced Epochs. + + (Python documentation hints)""" + + def __init__(self, start: Epoch, end: Epoch, step: Duration, inclusive: bool) -> TimeSeries: + """An iterator of a sequence of evenly spaced Epochs. + + (Python documentation hints)""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self):... + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __iter__(self) -> typing.Any: + """Implement iter(self).""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __next__(self) -> typing.Any: + """Implement next(self).""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + + @typing.final + class Unit: + """An Enum to perform time unit conversions.""" + + def from_seconds(self):... + + def in_seconds(self):... + + def __add__(): + """Return self+value.""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __int__(self) -> None: + """int(self)""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __radd__(): + """Return value+self.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __rmul__(): + """Return value*self.""" + + def __rsub__(): + """Return value-self.""" + + def __sub__(): + """Return self-value.""" + Century: Unit = ... + Day: Unit = ... + Hour: Unit = ... + Microsecond: Unit = ... + Millisecond: Unit = ... + Minute: Unit = ... + Nanosecond: Unit = ... + Second: Unit = ... + Week: Unit = ... + + @typing.final + class Ut1Provider: + """A structure storing all of the TAI-UT1 data""" + + def __init__(self) -> None: + """A structure storing all of the TAI-UT1 data""" + + def __repr__(self) -> str: + """Return repr(self).""" @typing.final class utils: @@ -438,4 +2194,5 @@ KPL/FK files must be converted into "PCA" (Planetary Constant ANISE) files befor """Converts two KPL/TPC files, one defining the planetary constants as text, and the other defining the gravity parameters, into the PlanetaryDataSet equivalent ANISE file. KPL/TPC files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE.""" __all__: list = ... - __name__: str = ... \ No newline at end of file + __name__: str = ... + diff --git a/anise-py/hifitime.pyi b/anise-py/hifitime.pyi new file mode 100644 index 00000000..537676c2 --- /dev/null +++ b/anise-py/hifitime.pyi @@ -0,0 +1,1087 @@ +import typing + +@typing.final +class Duration: + """Defines generally usable durations for nanosecond precision valid for 32,768 centuries in either direction, and only on 80 bits / 10 octets. + +**Important conventions:** +1. The negative durations can be mentally modeled "BC" years. One hours before 01 Jan 0000, it was "-1" years but 365 days and 23h into the current day. +It was decided that the nanoseconds corresponds to the nanoseconds _into_ the current century. In other words, +a duration with centuries = -1 and nanoseconds = 0 is _a greater duration_ (further from zero) than centuries = -1 and nanoseconds = 1. +Duration zero minus one nanosecond returns a century of -1 and a nanosecond set to the number of nanoseconds in one century minus one. +That difference is exactly 1 nanoseconds, where the former duration is "closer to zero" than the latter. +As such, the largest negative duration that can be represented sets the centuries to i16::MAX and its nanoseconds to NANOSECONDS_PER_CENTURY. +2. It was also decided that opposite durations are equal, e.g. -15 minutes == 15 minutes. If the direction of time matters, use the signum function. + +(Python documentation hints)""" + + def __init__(self, string_repr: str) -> Duration: + """Defines generally usable durations for nanosecond precision valid for 32,768 centuries in either direction, and only on 80 bits / 10 octets. + +**Important conventions:** +1. The negative durations can be mentally modeled "BC" years. One hours before 01 Jan 0000, it was "-1" years but 365 days and 23h into the current day. +It was decided that the nanoseconds corresponds to the nanoseconds _into_ the current century. In other words, +a duration with centuries = -1 and nanoseconds = 0 is _a greater duration_ (further from zero) than centuries = -1 and nanoseconds = 1. +Duration zero minus one nanosecond returns a century of -1 and a nanosecond set to the number of nanoseconds in one century minus one. +That difference is exactly 1 nanoseconds, where the former duration is "closer to zero" than the latter. +As such, the largest negative duration that can be represented sets the centuries to i16::MAX and its nanoseconds to NANOSECONDS_PER_CENTURY. +2. It was also decided that opposite durations are equal, e.g. -15 minutes == 15 minutes. If the direction of time matters, use the signum function. + +(Python documentation hints)""" + + @staticmethod + def EPSILON():... + + @staticmethod + def MAX():... + + @staticmethod + def MIN():... + + @staticmethod + def MIN_NEGATIVE():... + + @staticmethod + def MIN_POSITIVE():... + + @staticmethod + def ZERO():... + + def abs(self) -> Duration: + """Returns the absolute value of this duration""" + + def approx(self) -> Duration: + """Rounds this duration to the largest units represented in this duration. + +This is useful to provide an approximate human duration. Under the hood, this function uses `round`, +so the "tipping point" of the rounding is half way to the next increment of the greatest unit. +As shown below, one example is that 35 hours and 59 minutes rounds to 1 day, but 36 hours and 1 minute rounds +to 2 days because 2 days is closer to 36h 1 min than 36h 1 min is to 1 day. + +# Example + +``` +use hifitime::{Duration, TimeUnits}; + +assert_eq!((2.hours() + 3.minutes()).approx(), 2.hours()); +assert_eq!((24.hours() + 3.minutes()).approx(), 1.days()); +assert_eq!((35.hours() + 59.minutes()).approx(), 1.days()); +assert_eq!((36.hours() + 1.minutes()).approx(), 2.days()); +assert_eq!((47.hours() + 3.minutes()).approx(), 2.days()); +assert_eq!((49.hours() + 3.minutes()).approx(), 2.days()); +```""" + + def ceil(self, duration: Duration) -> Duration: + """Ceils this duration to the closest provided duration + +This simply floors then adds the requested duration + +# Example +``` +use hifitime::{Duration, TimeUnits}; + +let two_hours_three_min = 2.hours() + 3.minutes(); +assert_eq!(two_hours_three_min.ceil(1.hours()), 3.hours()); +assert_eq!(two_hours_three_min.ceil(30.minutes()), 2.hours() + 30.minutes()); +assert_eq!(two_hours_three_min.ceil(4.hours()), 4.hours()); +assert_eq!(two_hours_three_min.ceil(1.seconds()), two_hours_three_min + 1.seconds()); +assert_eq!(two_hours_three_min.ceil(1.hours() + 5.minutes()), 2.hours() + 10.minutes()); +```""" + + def decompose(self) -> typing.Tuple: + """Decomposes a Duration in its sign, days, hours, minutes, seconds, ms, us, ns""" + + def floor(self, duration: Duration) -> Duration: + """Floors this duration to the closest duration from the bottom + +# Example +``` +use hifitime::{Duration, TimeUnits}; + +let two_hours_three_min = 2.hours() + 3.minutes(); +assert_eq!(two_hours_three_min.floor(1.hours()), 2.hours()); +assert_eq!(two_hours_three_min.floor(30.minutes()), 2.hours()); +// This is zero because we floor by a duration longer than the current duration, rounding it down +assert_eq!(two_hours_three_min.floor(4.hours()), 0.hours()); +assert_eq!(two_hours_three_min.floor(1.seconds()), two_hours_three_min); +assert_eq!(two_hours_three_min.floor(1.hours() + 1.minutes()), 2.hours() + 2.minutes()); +assert_eq!(two_hours_three_min.floor(1.hours() + 5.minutes()), 1.hours() + 5.minutes()); +```""" + + @staticmethod + def from_all_parts(sign: int, days: int, hours: int, minutes: int, seconds: int, milliseconds: int, microseconds: int, nanoseconds: int) -> Duration: + """Creates a new duration from its parts""" + + @staticmethod + def from_parts(centuries: int, nanoseconds: int) -> Duration: + """Create a normalized duration from its parts""" + + @staticmethod + def from_total_nanoseconds(nanos: int) -> Duration: + """Creates a new Duration from its full nanoseconds""" + + def is_negative(self) -> bool: + """Returns whether this is a negative or positive duration.""" + + def max(self, other: Duration) -> Duration: + """Returns the maximum of the two durations. + +``` +use hifitime::TimeUnits; + +let d0 = 20.seconds(); +let d1 = 21.seconds(); + +assert_eq!(d1, d1.max(d0)); +assert_eq!(d1, d0.max(d1)); +```""" + + def min(self, other: Duration) -> Duration: + """Returns the minimum of the two durations. + +``` +use hifitime::TimeUnits; + +let d0 = 20.seconds(); +let d1 = 21.seconds(); + +assert_eq!(d0, d1.min(d0)); +assert_eq!(d0, d0.min(d1)); +```""" + + def round(self, duration: Duration) -> Duration: + """Rounds this duration to the closest provided duration + +This performs both a `ceil` and `floor` and returns the value which is the closest to current one. +# Example +``` +use hifitime::{Duration, TimeUnits}; + +let two_hours_three_min = 2.hours() + 3.minutes(); +assert_eq!(two_hours_three_min.round(1.hours()), 2.hours()); +assert_eq!(two_hours_three_min.round(30.minutes()), 2.hours()); +assert_eq!(two_hours_three_min.round(4.hours()), 4.hours()); +assert_eq!(two_hours_three_min.round(1.seconds()), two_hours_three_min); +assert_eq!(two_hours_three_min.round(1.hours() + 5.minutes()), 2.hours() + 10.minutes()); +```""" + + def signum(self) -> int: + """Returns the sign of this duration ++ 0 if the number is zero ++ 1 if the number is positive ++ -1 if the number is negative""" + + def to_parts(self) -> typing.Tuple: + """Returns the centuries and nanoseconds of this duration +NOTE: These items are not public to prevent incorrect durations from being created by modifying the values of the structure directly.""" + + def to_seconds(self) -> float: + """Returns this duration in seconds f64. +For high fidelity comparisons, it is recommended to keep using the Duration structure.""" + + def to_unit(self, unit: Unit) -> float:... + + def total_nanoseconds(self) -> int: + """Returns the total nanoseconds in a signed 128 bit integer""" + + def __add__(): + """Return self+value.""" + + def __div__():... + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self):... + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __radd__(): + """Return value+self.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __rmul__(): + """Return value*self.""" + + def __rsub__(): + """Return value-self.""" + + def __str__(self) -> str: + """Return str(self).""" + + def __sub__(): + """Return self-value.""" + +@typing.final +class DurationError: + __cause__: typing.Any + __context__: typing.Any + __suppress_context__: typing.Any + __traceback__: typing.Any + args: typing.Any + + def add_note(): + """Exception.add_note(note) -- +add a note to the exception""" + + def with_traceback(): + """Exception.with_traceback(tb) -- +set self.__traceback__ to tb and return self.""" + + def __delattr__(): + """Implement delattr(self, name).""" + + def __getattribute__(): + """Return getattr(self, name).""" + + def __init__(): + """Initialize self. See help(type(self)) for accurate signature.""" + + def __repr__(): + """Return repr(self).""" + + def __setattr__(): + """Implement setattr(self, name, value).""" + + def __setstate__():... + + def __str__(): + """Return str(self).""" + +@typing.final +class Epoch: + """Defines a nanosecond-precision Epoch. + +Refer to the appropriate functions for initializing this Epoch from different time scales or representations. + +(Python documentation hints)""" + + def __init__(self, string_repr: str) -> Epoch: + """Defines a nanosecond-precision Epoch. + +Refer to the appropriate functions for initializing this Epoch from different time scales or representations. + +(Python documentation hints)""" + + def day_of_year(self) -> float: + """Returns the number of days since the start of the year.""" + + def duration_in_year(self) -> Duration: + """Returns the duration since the start of the year""" + + def hours(self) -> int: + """Returns the hours of the Gregorian representation of this epoch in the time scale it was initialized in.""" + + @staticmethod + def init_from_bdt_days(days: float) -> Epoch: + """Initialize an Epoch from the number of days since the BeiDou Time Epoch, +defined as January 1st 2006 (cf. ).""" + + @staticmethod + def init_from_bdt_nanoseconds(nanoseconds: float) -> Epoch: + """Initialize an Epoch from the number of days since the BeiDou Time Epoch, +defined as January 1st 2006 (cf. ). +This may be useful for time keeping devices that use BDT as a time source.""" + + @staticmethod + def init_from_bdt_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the number of seconds since the BeiDou Time Epoch, +defined as January 1st 2006 (cf. ).""" + + @staticmethod + def init_from_et_duration(duration_since_j2000: Duration) -> Epoch: + """Initialize an Epoch from the Ephemeris Time duration past 2000 JAN 01 (J2000 reference)""" + + @staticmethod + def init_from_et_seconds(seconds_since_j2000: float) -> Epoch: + """Initialize an Epoch from the Ephemeris Time seconds past 2000 JAN 01 (J2000 reference)""" + + @staticmethod + def init_from_gpst_days(days: float) -> Epoch: + """Initialize an Epoch from the number of days since the GPS Time Epoch, +defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + @staticmethod + def init_from_gpst_nanoseconds(nanoseconds: float) -> Epoch: + """Initialize an Epoch from the number of nanoseconds since the GPS Time Epoch, +defined as UTC midnight of January 5th to 6th 1980 (cf. ). +This may be useful for time keeping devices that use GPS as a time source.""" + + @staticmethod + def init_from_gpst_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the number of seconds since the GPS Time Epoch, +defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + @staticmethod + def init_from_gregorian(year: int, month: int, day: int, hour: int, minute: int, second: int, nanos: int, time_scale: TimeScale) -> Epoch: + """Initialize from the Gregorian parts""" + + @staticmethod + def init_from_gregorian_at_midnight(year: int, month: int, day: int, time_scale: TimeScale) -> Epoch: + """Initialize from the Gregorian parts, time set to midnight""" + + @staticmethod + def init_from_gregorian_at_noon(year: int, month: int, day: int, time_scale: TimeScale) -> Epoch: + """Initialize from the Gregorian parts, time set to noon""" + + @staticmethod + def init_from_gregorian_utc(year: int, month: int, day: int, hour: int, minute: int, second: int, nanos: int) -> Epoch: + """Builds an Epoch from the provided Gregorian date and time in TAI. If invalid date is provided, this function will panic. +Use maybe_from_gregorian_tai if unsure.""" + + @staticmethod + def init_from_gst_days(days: float) -> Epoch: + """Initialize an Epoch from the number of days since the Galileo Time Epoch, +starting on August 21st 1999 Midnight UT, +(cf. ).""" + + @staticmethod + def init_from_gst_nanoseconds(nanoseconds: float) -> Epoch: + """Initialize an Epoch from the number of nanoseconds since the Galileo Time Epoch, +starting on August 21st 1999 Midnight UT, +(cf. ). +This may be useful for time keeping devices that use GST as a time source.""" + + @staticmethod + def init_from_gst_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the number of seconds since the Galileo Time Epoch, +starting on August 21st 1999 Midnight UT, +(cf. ).""" + + @staticmethod + def init_from_jde_et(days: float) -> Epoch: + """Initialize from the JDE days""" + + @staticmethod + def init_from_jde_tai(days: float) -> Epoch: + """Initialize an Epoch from given JDE in TAI time scale""" + + @staticmethod + def init_from_jde_tdb(days: float) -> Epoch: + """Initialize from Dynamic Barycentric Time (TDB) (same as SPICE ephemeris time) in JD days""" + + @staticmethod + def init_from_jde_utc(days: float) -> Epoch: + """Initialize an Epoch from given JDE in UTC time scale""" + + @staticmethod + def init_from_mjd_tai(days: float) -> Epoch: + """Initialize an Epoch from given MJD in TAI time scale""" + + @staticmethod + def init_from_mjd_utc(days: float) -> Epoch: + """Initialize an Epoch from given MJD in UTC time scale""" + + @staticmethod + def init_from_qzsst_days(days: float) -> Epoch: + """Initialize an Epoch from the number of days since the QZSS Time Epoch, +defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + @staticmethod + def init_from_qzsst_nanoseconds(nanoseconds: int) -> Epoch: + """Initialize an Epoch from the number of nanoseconds since the QZSS Time Epoch, +defined as UTC midnight of January 5th to 6th 1980 (cf. ). +This may be useful for time keeping devices that use QZSS as a time source.""" + + @staticmethod + def init_from_qzsst_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the number of seconds since the QZSS Time Epoch, +defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + @staticmethod + def init_from_tai_days(days: float) -> Epoch: + """Initialize an Epoch from the provided TAI days since 1900 January 01 at midnight""" + + @staticmethod + def init_from_tai_duration(duration: Duration) -> Epoch: + """Creates a new Epoch from a Duration as the time difference between this epoch and TAI reference epoch.""" + + @staticmethod + def init_from_tai_parts(centuries: int, nanoseconds: int) -> Epoch: + """Creates a new Epoch from its centuries and nanosecond since the TAI reference epoch.""" + + @staticmethod + def init_from_tai_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the provided TAI seconds since 1900 January 01 at midnight""" + + @staticmethod + def init_from_tdb_duration(duration_since_j2000: Duration) -> Epoch: + """Initialize from Dynamic Barycentric Time (TDB) (same as SPICE ephemeris time) whose epoch is 2000 JAN 01 noon TAI.""" + + @staticmethod + def init_from_tdb_seconds(seconds_j2000: float) -> Epoch: + """Initialize an Epoch from Dynamic Barycentric Time (TDB) seconds past 2000 JAN 01 midnight (difference than SPICE) +NOTE: This uses the ESA algorithm, which is a notch more complicated than the SPICE algorithm, but more precise. +In fact, SPICE algorithm is precise +/- 30 microseconds for a century whereas ESA algorithm should be exactly correct.""" + + @staticmethod + def init_from_tt_duration(duration: Duration) -> Epoch: + """Initialize an Epoch from the provided TT seconds (approximated to 32.184s delta from TAI)""" + + @staticmethod + def init_from_tt_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the provided TT seconds (approximated to 32.184s delta from TAI)""" + + @staticmethod + def init_from_unix_milliseconds(milliseconds: float) -> Epoch: + """Initialize an Epoch from the provided UNIX millisecond timestamp since UTC midnight 1970 January 01.""" + + @staticmethod + def init_from_unix_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the provided UNIX second timestamp since UTC midnight 1970 January 01.""" + + @staticmethod + def init_from_utc_days(days: float) -> Epoch: + """Initialize an Epoch from the provided UTC days since 1900 January 01 at midnight""" + + @staticmethod + def init_from_utc_seconds(seconds: float) -> Epoch: + """Initialize an Epoch from the provided UTC seconds since 1900 January 01 at midnight""" + + def isoformat(self) -> str: + """Equivalent to `datetime.isoformat`, and truncated to 23 chars, refer to for format options""" + + def leap_seconds(self, iers_only: bool) -> float: + """Get the accumulated number of leap seconds up to this Epoch accounting only for the IERS leap seconds and the SOFA scaling from 1960 to 1972, depending on flag. +Returns None if the epoch is before 1960, year at which UTC was defined. + +# Why does this function return an `Option` when the other returns a value +This is to match the `iauDat` function of SOFA (src/dat.c). That function will return a warning and give up if the start date is before 1960.""" + + def leap_seconds_iers(self) -> int: + """Get the accumulated number of leap seconds up to this Epoch accounting only for the IERS leap seconds.""" + + def leap_seconds_with_file(self, iers_only: bool, provider: LeapSecondsFile) -> float: + """Get the accumulated number of leap seconds up to this Epoch from the provided LeapSecondProvider. +Returns None if the epoch is before 1960, year at which UTC was defined. + +# Why does this function return an `Option` when the other returns a value +This is to match the `iauDat` function of SOFA (src/dat.c). That function will return a warning and give up if the start date is before 1960.""" + + def microseconds(self) -> int: + """Returns the microseconds of the Gregorian representation of this epoch in the time scale it was initialized in.""" + + def milliseconds(self) -> int: + """Returns the milliseconds of the Gregorian representation of this epoch in the time scale it was initialized in.""" + + def minutes(self) -> int: + """Returns the minutes of the Gregorian representation of this epoch in the time scale it was initialized in.""" + + def month_name(self) -> MonthName:... + + def nanoseconds(self) -> int: + """Returns the nanoseconds of the Gregorian representation of this epoch in the time scale it was initialized in.""" + + def seconds(self) -> int: + """Returns the seconds of the Gregorian representation of this epoch in the time scale it was initialized in.""" + + def strftime(self, format_str: str) -> str: + """Equivalent to `datetime.strftime`, refer to for format options""" + + @staticmethod + def strptime(epoch_str: str, format_str: str) -> Epoch: + """Equivalent to `datetime.strptime`, refer to for format options""" + + @staticmethod + def system_now() -> Epoch: + """Returns the computer clock in UTC""" + + def timedelta(self, other: Duration) -> Duration: + """Differences between two epochs""" + + def to_bdt_days(self) -> float: + """Returns days past BDT (BeiDou) Time Epoch, defined as Jan 01 2006 UTC +(cf. ).""" + + def to_bdt_duration(self) -> Duration: + """Returns `Duration` past BDT (BeiDou) time Epoch.""" + + def to_bdt_nanoseconds(self) -> int: + """Returns nanoseconds past BDT (BeiDou) Time Epoch, defined as Jan 01 2006 UTC +(cf. ). +NOTE: This function will return an error if the centuries past GST time are not zero.""" + + def to_bdt_seconds(self) -> float: + """Returns seconds past BDT (BeiDou) Time Epoch""" + + def to_duration_in_time_scale(self, ts: TimeScale) -> Duration: + """Returns this epoch with respect to the provided time scale. +This is needed to correctly perform duration conversions in dynamical time scales (e.g. TDB).""" + + def to_et_centuries_since_j2000(self) -> float: + """Returns the number of centuries since Ephemeris Time (ET) J2000 (used for Archinal et al. rotations)""" + + def to_et_days_since_j2000(self) -> float: + """Returns the number of days since Ephemeris Time (ET) J2000 (used for Archinal et al. rotations)""" + + def to_et_duration(self) -> Duration: + """Returns the duration between J2000 and the current epoch as per NAIF SPICE. + +# Warning +The et2utc function of NAIF SPICE will assume that there are 9 leap seconds before 01 JAN 1972, +as this date introduces 10 leap seconds. At the time of writing, this does _not_ seem to be in +line with IERS and the documentation in the leap seconds list. + +In order to match SPICE, the as_et_duration() function will manually get rid of that difference.""" + + def to_et_seconds(self) -> float: + """Returns the Ephemeris Time seconds past 2000 JAN 01 midnight, matches NASA/NAIF SPICE.""" + + def to_gpst_days(self) -> float: + """Returns days past GPS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + def to_gpst_duration(self) -> Duration: + """Returns `Duration` past GPS time Epoch.""" + + def to_gpst_nanoseconds(self) -> int: + """Returns nanoseconds past GPS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ). +NOTE: This function will return an error if the centuries past GPST time are not zero.""" + + def to_gpst_seconds(self) -> float: + """Returns seconds past GPS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + def to_gst_days(self) -> float: + """Returns days past GST (Galileo) Time Epoch, +starting on August 21st 1999 Midnight UT +(cf. ).""" + + def to_gst_duration(self) -> Duration: + """Returns `Duration` past GST (Galileo) time Epoch.""" + + def to_gst_nanoseconds(self) -> int: + """Returns nanoseconds past GST (Galileo) Time Epoch, starting on August 21st 1999 Midnight UT +(cf. ). +NOTE: This function will return an error if the centuries past GST time are not zero.""" + + def to_gst_seconds(self) -> float: + """Returns seconds past GST (Galileo) Time Epoch""" + + def to_isoformat(self) -> str: + """The standard ISO format of this epoch (six digits of subseconds) in the _current_ time scale, refer to for format options.""" + + def to_jde_et(self, unit: Unit) -> float:... + + def to_jde_et_days(self) -> float: + """Returns the Ephemeris Time JDE past epoch""" + + def to_jde_et_duration(self) -> Duration:... + + def to_jde_tai(self, unit: Unit) -> float: + """Returns the Julian Days from epoch 01 Jan -4713 12:00 (noon) in desired Duration::Unit""" + + def to_jde_tai_days(self) -> float: + """Returns the Julian days from epoch 01 Jan -4713, 12:00 (noon) +as explained in "Fundamentals of astrodynamics and applications", Vallado et al. +4th edition, page 182, and on [Wikipedia](https://en.wikipedia.org/wiki/Julian_day).""" + + def to_jde_tai_duration(self) -> Duration: + """Returns the Julian Days from epoch 01 Jan -4713 12:00 (noon) as a Duration""" + + def to_jde_tai_seconds(self) -> float: + """Returns the Julian seconds in TAI.""" + + def to_jde_tdb_days(self) -> float: + """Returns the Dynamic Barycentric Time (TDB) (higher fidelity SPICE ephemeris time) whose epoch is 2000 JAN 01 noon TAI (cf. )""" + + def to_jde_tdb_duration(self) -> Duration:... + + def to_jde_tt_days(self) -> float: + """Returns days past Julian epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT))""" + + def to_jde_tt_duration(self) -> Duration:... + + def to_jde_utc_days(self) -> float: + """Returns the Julian days in UTC.""" + + def to_jde_utc_duration(self) -> Duration: + """Returns the Julian days in UTC as a `Duration`""" + + def to_jde_utc_seconds(self) -> float: + """Returns the Julian Days in UTC seconds.""" + + def to_mjd_tai(self, unit: Unit) -> float: + """Returns this epoch as a duration in the requested units in MJD TAI""" + + def to_mjd_tai_days(self) -> float: + """`as_mjd_days` creates an Epoch from the provided Modified Julian Date in days as explained +[here](http://tycho.usno.navy.mil/mjd.html). MJD epoch is Modified Julian Day at 17 November 1858 at midnight.""" + + def to_mjd_tai_seconds(self) -> float: + """Returns the Modified Julian Date in seconds TAI.""" + + def to_mjd_tt_days(self) -> float: + """Returns days past Modified Julian epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT))""" + + def to_mjd_tt_duration(self) -> Duration:... + + def to_mjd_utc(self, unit: Unit) -> float: + """Returns the Modified Julian Date in the provided unit in UTC.""" + + def to_mjd_utc_days(self) -> float: + """Returns the Modified Julian Date in days UTC.""" + + def to_mjd_utc_seconds(self) -> float: + """Returns the Modified Julian Date in seconds UTC.""" + + def to_nanoseconds_in_time_scale(self, time_scale: TimeScale) -> int: + """Attempts to return the number of nanoseconds since the reference epoch of the provided time scale. +This will return an overflow error if more than one century has past since the reference epoch in the provided time scale. +If this is _not_ an issue, you should use `epoch.to_duration_in_time_scale().to_parts()` to retrieve both the centuries and the nanoseconds +in that century.""" + + def to_qzsst_days(self) -> float: + """Returns days past QZSS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + def to_qzsst_duration(self) -> Duration: + """Returns `Duration` past QZSS time Epoch.""" + + def to_qzsst_nanoseconds(self) -> int: + """Returns nanoseconds past QZSS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ). +NOTE: This function will return an error if the centuries past QZSST time are not zero.""" + + def to_qzsst_seconds(self) -> float: + """Returns seconds past QZSS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" + + def to_rfc3339(self) -> str: + """Returns this epoch in UTC in the RFC3339 format""" + + def to_tai(self, unit: Unit) -> float: + """Returns the epoch as a floating point value in the provided unit""" + + def to_tai_days(self) -> float: + """Returns the number of days since J1900 in TAI""" + + def to_tai_duration(self) -> Duration: + """Returns this time in a Duration past J1900 counted in TAI""" + + def to_tai_parts(self) -> typing.Tuple: + """Returns the TAI parts of this duration""" + + def to_tai_seconds(self) -> float: + """Returns the number of TAI seconds since J1900""" + + def to_tdb_centuries_since_j2000(self) -> float: + """Returns the number of centuries since Dynamic Barycentric Time (TDB) J2000 (used for Archinal et al. rotations)""" + + def to_tdb_days_since_j2000(self) -> float: + """Returns the number of days since Dynamic Barycentric Time (TDB) J2000 (used for Archinal et al. rotations)""" + + def to_tdb_duration(self) -> Duration: + """Returns the Dynamics Barycentric Time (TDB) as a high precision Duration since J2000 + +## Algorithm +Given the embedded sine functions in the equation to compute the difference between TDB and TAI from the number of TDB seconds +past J2000, one cannot solve the revert the operation analytically. Instead, we iterate until the value no longer changes. + +1. Assume that the TAI duration is in fact the TDB seconds from J2000. +2. Offset to J2000 because `Epoch` stores everything in the J1900 but the TDB duration is in J2000. +3. Compute the offset `g` due to the TDB computation with the current value of the TDB seconds (defined in step 1). +4. Subtract that offset to the latest TDB seconds and store this as a new candidate for the true TDB seconds value. +5. Compute the difference between this candidate and the previous one. If the difference is less than one nanosecond, stop iteration. +6. Set the new candidate as the TDB seconds since J2000 and loop until step 5 breaks the loop, or we've done five iterations. +7. At this stage, we have a good approximation of the TDB seconds since J2000. +8. Reverse the algorithm given that approximation: compute the `g` offset, compute the difference between TDB and TAI, add the TT offset (32.184 s), and offset by the difference between J1900 and J2000.""" + + def to_tdb_seconds(self) -> float: + """Returns the Dynamic Barycentric Time (TDB) (higher fidelity SPICE ephemeris time) whose epoch is 2000 JAN 01 noon TAI (cf. )""" + + def to_time_scale(self, ts: TimeScale) -> Epoch: + """Converts self to another time scale + +As per the [Rust naming convention](https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv), +this borrows an Epoch and returns an owned Epoch.""" + + def to_tt_centuries_j2k(self) -> float: + """Returns the centuries passed J2000 TT""" + + def to_tt_days(self) -> float: + """Returns days past TAI epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT))""" + + def to_tt_duration(self) -> Duration: + """Returns `Duration` past TAI epoch in Terrestrial Time (TT).""" + + def to_tt_seconds(self) -> float: + """Returns seconds past TAI epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT))""" + + def to_tt_since_j2k(self) -> Duration: + """Returns the duration past J2000 TT""" + + def to_unix(self, unit: Unit) -> float: + """Returns the duration since the UNIX epoch in the provided unit.""" + + def to_unix_days(self) -> float: + """Returns the number days since the UNIX epoch defined 01 Jan 1970 midnight UTC.""" + + def to_unix_duration(self) -> Duration: + """Returns the Duration since the UNIX epoch UTC midnight 01 Jan 1970.""" + + def to_unix_milliseconds(self) -> float: + """Returns the number milliseconds since the UNIX epoch defined 01 Jan 1970 midnight UTC.""" + + def to_unix_seconds(self) -> float: + """Returns the number seconds since the UNIX epoch defined 01 Jan 1970 midnight UTC.""" + + def to_utc(self, unit: Unit) -> float: + """Returns the number of UTC seconds since the TAI epoch""" + + def to_utc_days(self) -> float: + """Returns the number of UTC days since the TAI epoch""" + + def to_utc_duration(self) -> Duration: + """Returns this time in a Duration past J1900 counted in UTC""" + + def to_utc_seconds(self) -> float: + """Returns the number of UTC seconds since the TAI epoch""" + + def year(self) -> int: + """Returns the number of Gregorian years of this epoch in the current time scale.""" + + def year_days_of_year(self) -> typing.Tuple: + """Returns the year and the days in the year so far (days of year).""" + + def __add__(): + """Return self+value.""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self):... + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __radd__(): + """Return value+self.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __rsub__(): + """Return value-self.""" + + def __str__(self) -> str: + """Return str(self).""" + + def __sub__(): + """Return self-value.""" + +@typing.final +class HifitimeError: + __cause__: typing.Any + __context__: typing.Any + __suppress_context__: typing.Any + __traceback__: typing.Any + args: typing.Any + + def add_note(): + """Exception.add_note(note) -- +add a note to the exception""" + + def with_traceback(): + """Exception.with_traceback(tb) -- +set self.__traceback__ to tb and return self.""" + + def __delattr__(): + """Implement delattr(self, name).""" + + def __getattribute__(): + """Return getattr(self, name).""" + + def __init__(): + """Initialize self. See help(type(self)) for accurate signature.""" + + def __repr__(): + """Return repr(self).""" + + def __setattr__(): + """Implement setattr(self, name, value).""" + + def __setstate__():... + + def __str__(): + """Return str(self).""" + +@typing.final +class LatestLeapSeconds: + """List of leap seconds from https://www.ietf.org/timezones/data/leap-seconds.list . +This list corresponds the number of seconds in TAI to the UTC offset and to whether it was an announced leap second or not. +The unannoucned leap seconds come from dat.c in the SOFA library.""" + + def __init__(self) -> None: + """List of leap seconds from https://www.ietf.org/timezones/data/leap-seconds.list . +This list corresponds the number of seconds in TAI to the UTC offset and to whether it was an announced leap second or not. +The unannoucned leap seconds come from dat.c in the SOFA library.""" + + def __repr__(self) -> str: + """Return repr(self).""" + +@typing.final +class LeapSecondsFile: + """A leap second provider that uses an IERS formatted leap seconds file. + +(Python documentation hints)""" + + def __init__(self, path: str) -> LeapSecondsFile: + """A leap second provider that uses an IERS formatted leap seconds file. + +(Python documentation hints)""" + + def __repr__(self) -> str: + """Return repr(self).""" + +@typing.final +class MonthName: + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __int__(self) -> None: + """int(self)""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + April: MonthName = ... + August: MonthName = ... + December: MonthName = ... + February: MonthName = ... + January: MonthName = ... + July: MonthName = ... + June: MonthName = ... + March: MonthName = ... + May: MonthName = ... + November: MonthName = ... + October: MonthName = ... + September: MonthName = ... + +@typing.final +class ParsingError: + __cause__: typing.Any + __context__: typing.Any + __suppress_context__: typing.Any + __traceback__: typing.Any + args: typing.Any + + def add_note(): + """Exception.add_note(note) -- +add a note to the exception""" + + def with_traceback(): + """Exception.with_traceback(tb) -- +set self.__traceback__ to tb and return self.""" + + def __delattr__(): + """Implement delattr(self, name).""" + + def __getattribute__(): + """Return getattr(self, name).""" + + def __init__(): + """Initialize self. See help(type(self)) for accurate signature.""" + + def __repr__(): + """Return repr(self).""" + + def __setattr__(): + """Implement setattr(self, name, value).""" + + def __setstate__():... + + def __str__(): + """Return str(self).""" + +@typing.final +class TimeScale: + """Enum of the different time systems available""" + + def uses_leap_seconds(self) -> bool: + """Returns true if self takes leap seconds into account""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __int__(self) -> None: + """int(self)""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __repr__(self) -> str: + """Return repr(self).""" + BDT: TimeScale = ... + ET: TimeScale = ... + GPST: TimeScale = ... + GST: TimeScale = ... + QZSST: TimeScale = ... + TAI: TimeScale = ... + TDB: TimeScale = ... + TT: TimeScale = ... + UTC: TimeScale = ... + +@typing.final +class TimeSeries: + """An iterator of a sequence of evenly spaced Epochs. + +(Python documentation hints)""" + + def __init__(self, start: Epoch, end: Epoch, step: Duration, inclusive: bool) -> TimeSeries: + """An iterator of a sequence of evenly spaced Epochs. + +(Python documentation hints)""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __getnewargs__(self):... + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __iter__(self) -> typing.Any: + """Implement iter(self).""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __next__(self) -> typing.Any: + """Implement next(self).""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __str__(self) -> str: + """Return str(self).""" + +@typing.final +class Unit: + """An Enum to perform time unit conversions.""" + + def from_seconds(self):... + + def in_seconds(self):... + + def __add__(): + """Return self+value.""" + + def __eq__(self, value: typing.Any) -> bool: + """Return self==value.""" + + def __ge__(self, value: typing.Any) -> bool: + """Return self>=value.""" + + def __gt__(self, value: typing.Any) -> bool: + """Return self>value.""" + + def __int__(self) -> None: + """int(self)""" + + def __le__(self, value: typing.Any) -> bool: + """Return self<=value.""" + + def __lt__(self, value: typing.Any) -> bool: + """Return self bool: + """Return self!=value.""" + + def __radd__(): + """Return value+self.""" + + def __repr__(self) -> str: + """Return repr(self).""" + + def __rmul__(): + """Return value*self.""" + + def __rsub__(): + """Return value-self.""" + + def __sub__(): + """Return self-value.""" + Century: Unit = ... + Day: Unit = ... + Hour: Unit = ... + Microsecond: Unit = ... + Millisecond: Unit = ... + Minute: Unit = ... + Nanosecond: Unit = ... + Second: Unit = ... + Week: Unit = ... + +@typing.final +class Ut1Provider: + """A structure storing all of the TAI-UT1 data""" + + def __init__(self) -> None: + """A structure storing all of the TAI-UT1 data""" + + def __repr__(self) -> str: + """Return repr(self).""" \ No newline at end of file From c7161004eb71488fbed770521ba0a59956ca6032 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Fri, 1 Nov 2024 22:18:16 -0600 Subject: [PATCH 6/6] Merge all type hints --- anise-py/anise.astro.constants.pyi | 75 -- anise-py/anise.astro.pyi | 615 ---------------- anise-py/anise.pyi | 4 + anise-py/anise.time.pyi | 0 anise-py/anise.utils.pyi | 9 - anise-py/hifitime.pyi | 1087 ---------------------------- 6 files changed, 4 insertions(+), 1786 deletions(-) delete mode 100644 anise-py/anise.astro.constants.pyi delete mode 100644 anise-py/anise.astro.pyi delete mode 100644 anise-py/anise.time.pyi delete mode 100644 anise-py/anise.utils.pyi delete mode 100644 anise-py/hifitime.pyi diff --git a/anise-py/anise.astro.constants.pyi b/anise-py/anise.astro.constants.pyi deleted file mode 100644 index 4b920c26..00000000 --- a/anise-py/anise.astro.constants.pyi +++ /dev/null @@ -1,75 +0,0 @@ -import typing - -@typing.final -class CelestialObjects: - EARTH: int = ... - EARTH_MOON_BARYCENTER: int = ... - JUPITER: int = ... - JUPITER_BARYCENTER: int = ... - MARS: int = ... - MARS_BARYCENTER: int = ... - MERCURY: int = ... - MOON: int = ... - NEPTUNE: int = ... - NEPTUNE_BARYCENTER: int = ... - PLUTO_BARYCENTER: int = ... - SATURN: int = ... - SATURN_BARYCENTER: int = ... - SOLAR_SYSTEM_BARYCENTER: int = ... - SUN: int = ... - URANUS: int = ... - URANUS_BARYCENTER: int = ... - VENUS: int = ... - -@typing.final -class Frames: - EARTH_ECLIPJ2000: Frame = ... - EARTH_ITRF93: Frame = ... - EARTH_J2000: Frame = ... - EARTH_MOON_BARYCENTER_J2000: Frame = ... - EME2000: Frame = ... - IAU_EARTH_FRAME: Frame = ... - IAU_JUPITER_FRAME: Frame = ... - IAU_MARS_FRAME: Frame = ... - IAU_MERCURY_FRAME: Frame = ... - IAU_MOON_FRAME: Frame = ... - IAU_NEPTUNE_FRAME: Frame = ... - IAU_SATURN_FRAME: Frame = ... - IAU_URANUS_FRAME: Frame = ... - IAU_VENUS_FRAME: Frame = ... - JUPITER_BARYCENTER_J2000: Frame = ... - MARS_BARYCENTER_J2000: Frame = ... - MERCURY_J2000: Frame = ... - MOON_J2000: Frame = ... - MOON_ME_FRAME: Frame = ... - MOON_PA_FRAME: Frame = ... - NEPTUNE_BARYCENTER_J2000: Frame = ... - PLUTO_BARYCENTER_J2000: Frame = ... - SATURN_BARYCENTER_J2000: Frame = ... - SSB_J2000: Frame = ... - SUN_J2000: Frame = ... - URANUS_BARYCENTER_J2000: Frame = ... - VENUS_J2000: Frame = ... - -@typing.final -class Orientations: - ECLIPJ2000: int = ... - IAU_EARTH: int = ... - IAU_JUPITER: int = ... - IAU_MARS: int = ... - IAU_MERCURY: int = ... - IAU_MOON: int = ... - IAU_NEPTUNE: int = ... - IAU_SATURN: int = ... - IAU_URANUS: int = ... - IAU_VENUS: int = ... - ITRF93: int = ... - J2000: int = ... - MOON_ME: int = ... - MOON_PA: int = ... - -@typing.final -class UsualConstants: - MEAN_EARTH_ANGULAR_VELOCITY_DEG_S: float = ... - MEAN_MOON_ANGULAR_VELOCITY_DEG_S: float = ... - SPEED_OF_LIGHT_KM_S: float = ... \ No newline at end of file diff --git a/anise-py/anise.astro.pyi b/anise-py/anise.astro.pyi deleted file mode 100644 index 3aa20b7b..00000000 --- a/anise-py/anise.astro.pyi +++ /dev/null @@ -1,615 +0,0 @@ -import typing - -@typing.final -class AzElRange: - """A structure that stores the result of Azimuth, Elevation, Range, Range rate calculation.""" - azimuth_deg: float - elevation_deg: float - epoch: Epoch - light_time: Duration - obstructed_by: Frame - range_km: float - range_rate_km_s: float - - def __init__(self, epoch: Epoch, azimuth_deg: float, elevation_deg: float, range_km: float, range_rate_km_s: float, obstructed_by: Frame=None) -> AzElRange: - """A structure that stores the result of Azimuth, Elevation, Range, Range rate calculation.""" - - def is_obstructed(self) -> bool: - """Returns whether there is an obstruction.""" - - def is_valid(self) -> bool: - """Returns false if the range is less than one millimeter, or any of the angles are NaN.""" - - def __eq__(self, value: typing.Any) -> bool: - """Return self==value.""" - - def __ge__(self, value: typing.Any) -> bool: - """Return self>=value.""" - - def __getnewargs__(self) -> typing.Tuple: - """Allows for pickling the object""" - - def __gt__(self, value: typing.Any) -> bool: - """Return self>value.""" - - def __le__(self, value: typing.Any) -> bool: - """Return self<=value.""" - - def __lt__(self, value: typing.Any) -> bool: - """Return self bool: - """Return self!=value.""" - - def __repr__(self) -> str: - """Return repr(self).""" - - def __str__(self) -> str: - """Return str(self).""" - -@typing.final -class Ellipsoid: - """Only the tri-axial Ellipsoid shape model is currently supported by ANISE. -This is directly inspired from SPICE PCK. -> For each body, three radii are listed: The first number is -> the largest equatorial radius (the length of the semi-axis -> containing the prime meridian), the second number is the smaller -> equatorial radius, and the third is the polar radius. - -Example: Radii of the Earth. - -BODY399_RADII = ( 6378.1366 6378.1366 6356.7519 )""" - polar_radius_km: float - semi_major_equatorial_radius_km: float - semi_minor_equatorial_radius_km: float - - def __init__(self, semi_major_equatorial_radius_km: float, polar_radius_km: float=None, semi_minor_equatorial_radius_km: float=None) -> Ellipsoid: - """Only the tri-axial Ellipsoid shape model is currently supported by ANISE. -This is directly inspired from SPICE PCK. -> For each body, three radii are listed: The first number is -> the largest equatorial radius (the length of the semi-axis -> containing the prime meridian), the second number is the smaller -> equatorial radius, and the third is the polar radius. - -Example: Radii of the Earth. - -BODY399_RADII = ( 6378.1366 6378.1366 6356.7519 )""" - - def flattening(self) -> float: - """Returns the flattening ratio, computed from the mean equatorial radius and the polar radius""" - - def is_sphere(self) -> bool: - """Returns true if the polar radius is equal to the semi minor radius.""" - - def is_spheroid(self) -> bool: - """Returns true if the semi major and minor radii are equal""" - - def mean_equatorial_radius_km(self) -> float: - """Returns the mean equatorial radius in kilometers""" - - def __eq__(self, value: typing.Any) -> bool: - """Return self==value.""" - - def __ge__(self, value: typing.Any) -> bool: - """Return self>=value.""" - - def __getnewargs__(self) -> typing.Tuple: - """Allows for pickling the object""" - - def __gt__(self, value: typing.Any) -> bool: - """Return self>value.""" - - def __le__(self, value: typing.Any) -> bool: - """Return self<=value.""" - - def __lt__(self, value: typing.Any) -> bool: - """Return self bool: - """Return self!=value.""" - - def __repr__(self) -> str: - """Return repr(self).""" - - def __str__(self) -> str: - """Return str(self).""" - -@typing.final -class Frame: - """A Frame uniquely defined by its ephemeris center and orientation. Refer to FrameDetail for frames combined with parameters.""" - ephemeris_id: int - orientation_id: int - shape: Ellipsoid - - def __init__(self, ephemeris_id: int, orientation_id: int, mu_km3_s2: float=None, shape: Ellipsoid=None) -> Frame: - """A Frame uniquely defined by its ephemeris center and orientation. Refer to FrameDetail for frames combined with parameters.""" - - def ephem_origin_id_match(self, other_id: int) -> bool: - """Returns true if the ephemeris origin is equal to the provided ID""" - - def ephem_origin_match(self, other: Frame) -> bool: - """Returns true if the ephemeris origin is equal to the provided frame""" - - def flattening(self) -> float: - """Returns the flattening ratio (unitless)""" - - def is_celestial(self) -> bool: - """Returns whether this is a celestial frame""" - - def is_geodetic(self) -> bool: - """Returns whether this is a geodetic frame""" - - def mean_equatorial_radius_km(self) -> float: - """Returns the mean equatorial radius in km, if defined""" - - def mu_km3_s2(self) -> float: - """Returns the gravitational parameters of this frame, if defined""" - - def orient_origin_id_match(self, other_id: int) -> bool: - """Returns true if the orientation origin is equal to the provided ID""" - - def orient_origin_match(self, other: Frame) -> bool: - """Returns true if the orientation origin is equal to the provided frame""" - - def polar_radius_km(self) -> float: - """Returns the polar radius in km, if defined""" - - def semi_major_radius_km(self) -> float: - """Returns the semi major radius of the tri-axial ellipoid shape of this frame, if defined""" - - def strip(self) -> None: - """Removes the graviational parameter and the shape information from this frame. -Use this to prevent astrodynamical computations.""" - - def with_ephem(self, new_ephem_id: int) -> Frame: - """Returns a copy of this Frame whose ephemeris ID is set to the provided ID""" - - def with_mu_km3_s2(self, mu_km3_s2: float) -> Frame: - """Returns a copy of this frame with the graviational parameter set to the new value.""" - - def with_orient(self, new_orient_id: int) -> Frame: - """Returns a copy of this Frame whose orientation ID is set to the provided ID""" - - def __eq__(self, value: typing.Any) -> bool: - """Return self==value.""" - - def __ge__(self, value: typing.Any) -> bool: - """Return self>=value.""" - - def __getnewargs__(self) -> typing.Tuple: - """Allows for pickling the object""" - - def __gt__(self, value: typing.Any) -> bool: - """Return self>value.""" - - def __le__(self, value: typing.Any) -> bool: - """Return self<=value.""" - - def __lt__(self, value: typing.Any) -> bool: - """Return self bool: - """Return self!=value.""" - - def __repr__(self) -> str: - """Return repr(self).""" - - def __str__(self) -> str: - """Return str(self).""" - -@typing.final -class Occultation: - """Stores the result of an occultation computation with the occulation percentage -Refer to the [MathSpec](https://nyxspace.com/nyxspace/MathSpec/celestial/eclipse/) for modeling details.""" - back_frame: Frame - epoch: Epoch - front_frame: Frame - percentage: float - - def factor(self) -> float: - """Returns the percentage as a factor between 0 and 1""" - - def is_eclipse_computation(self) -> bool: - """Returns true if the back object is the Sun, false otherwise""" - - def is_obstructed(self) -> bool: - """Returns true if the occultation percentage is greater than or equal 99.999%""" - - def is_partial(self) -> bool: - """Returns true if neither occulted nor visible (i.e. penumbra for solar eclipsing)""" - - def is_visible(self) -> bool: - """Returns true if the occultation percentage is less than or equal 0.001%""" - - def __repr__(self) -> str: - """Return repr(self).""" - - def __str__(self) -> str: - """Return str(self).""" - -@typing.final -class Orbit: - """Defines a Cartesian state in a given frame at a given epoch in a given time scale. Radius data is expressed in kilometers. Velocity data is expressed in kilometers per second. -Regardless of the constructor used, this struct stores all the state information in Cartesian coordinates as these are always non singular. - -Unless noted otherwise, algorithms are from GMAT 2016a [StateConversionUtil.cpp](https://github.com/ChristopherRabotin/GMAT/blob/37201a6290e7f7b941bc98ee973a527a5857104b/src/base/util/StateConversionUtil.cpp).""" - epoch: Epoch - frame: Frame - vx_km_s: float - vy_km_s: float - vz_km: None - vz_km_s: float - x_km: float - y_km: float - z_km: float - - def __init__(self, x_km: float, y_km: float, z_km: float, vx_km_s: float, vy_km_s: float, vz_km_s: float, epoch: Epoch, frame: Frame) -> Orbit: - """Defines a Cartesian state in a given frame at a given epoch in a given time scale. Radius data is expressed in kilometers. Velocity data is expressed in kilometers per second. -Regardless of the constructor used, this struct stores all the state information in Cartesian coordinates as these are always non singular. - -Unless noted otherwise, algorithms are from GMAT 2016a [StateConversionUtil.cpp](https://github.com/ChristopherRabotin/GMAT/blob/37201a6290e7f7b941bc98ee973a527a5857104b/src/base/util/StateConversionUtil.cpp).""" - - def abs_difference(self, other: Orbit) -> typing.Tuple: - """Returns the absolute position and velocity differences in km and km/s between this orbit and another. -Raises an error if the frames do not match (epochs do not need to match).""" - - def abs_pos_diff_km(self, other: Orbit) -> float: - """Returns the absolute position difference in kilometer between this orbit and another. -Raises an error if the frames do not match (epochs do not need to match).""" - - def abs_vel_diff_km_s(self, other: Orbit) -> float: - """Returns the absolute velocity difference in kilometer per second between this orbit and another. -Raises an error if the frames do not match (epochs do not need to match).""" - - def add_aop_deg(self, delta_aop_deg: float) -> Orbit: - """Returns a copy of the state with a provided AOP added to the current one""" - - def add_apoapsis_periapsis_km(self, delta_ra_km: float, delta_rp_km: float) -> Orbit: - """Returns a copy of this state with the provided apoasis and periapsis added to the current values""" - - def add_ecc(self, delta_ecc: float) -> Orbit: - """Returns a copy of the state with a provided ECC added to the current one""" - - def add_inc_deg(self, delta_inc_deg: float) -> None: - """Returns a copy of the state with a provided INC added to the current one""" - - def add_raan_deg(self, delta_raan_deg: float) -> Orbit: - """Returns a copy of the state with a provided RAAN added to the current one""" - - def add_sma_km(self, delta_sma_km: float) -> Orbit: - """Returns a copy of the state with a provided SMA added to the current one""" - - def add_ta_deg(self, delta_ta_deg: float) -> Orbit: - """Returns a copy of the state with a provided TA added to the current one""" - - def aol_deg(self) -> float: - """Returns the argument of latitude in degrees - -NOTE: If the orbit is near circular, the AoL will be computed from the true longitude -instead of relying on the ill-defined true anomaly.""" - - def aop_deg(self) -> float: - """Returns the argument of periapsis in degrees""" - - def apoapsis_altitude_km(self) -> float: - """Returns the altitude of apoapsis (or apogee around Earth), in kilometers.""" - - def apoapsis_km(self) -> float: - """Returns the radius of apoapsis (or apogee around Earth), in kilometers.""" - - def at_epoch(self, new_epoch: Epoch) -> Orbit: - """Adjusts the true anomaly of this orbit using the mean anomaly. - -# Astrodynamics note -This is not a true propagation of the orbit. This is akin to a two body propagation ONLY without any other force models applied. -Use Nyx for high fidelity propagation.""" - - def c3_km2_s2(self) -> float: - """Returns the $C_3$ of this orbit in km^2/s^2""" - - def declination_deg(self) -> float: - """Returns the declination of this orbit in degrees""" - - def distance_to_km(self, other: Orbit) -> float: - """Returns the distance in kilometers between this state and another state, if both frame match (epoch does not need to match).""" - - def ea_deg(self) -> float: - """Returns the eccentric anomaly in degrees - -This is a conversion from GMAT's StateConversionUtil::TrueToEccentricAnomaly""" - - def ecc(self) -> float: - """Returns the eccentricity (no unit)""" - - def energy_km2_s2(self) -> float: - """Returns the specific mechanical energy in km^2/s^2""" - - def eq_within(self, other: Orbit, radial_tol_km: float, velocity_tol_km_s: float) -> bool: - """Returns whether this orbit and another are equal within the specified radial and velocity absolute tolerances""" - - def fpa_deg(self) -> float: - """Returns the flight path angle in degrees""" - - @staticmethod - def from_cartesian(x_km: float, y_km: float, z_km: float, vx_km_s: float, vy_km_s: float, vz_km_s: float, epoch: Epoch, frame: Frame) -> Orbit: - """Creates a new Cartesian state in the provided frame at the provided Epoch. - -**Units:** km, km, km, km/s, km/s, km/s""" - - @staticmethod - def from_keplerian(sma_km: float, ecc: float, inc_deg: float, raan_deg: float, aop_deg: float, ta_deg: float, epoch: Epoch, frame: Frame) -> Orbit: - """Creates a new Orbit around the provided Celestial or Geoid frame from the Keplerian orbital elements. - -**Units:** km, none, degrees, degrees, degrees, degrees - -NOTE: The state is defined in Cartesian coordinates as they are non-singular. This causes rounding -errors when creating a state from its Keplerian orbital elements (cf. the state tests). -One should expect these errors to be on the order of 1e-12.""" - - @staticmethod - def from_keplerian_altitude(sma_altitude_km: float, ecc: float, inc_deg: float, raan_deg: float, aop_deg: float, ta_deg: float, epoch: Epoch, frame: Frame) -> Orbit: - """Creates a new Orbit from the provided semi-major axis altitude in kilometers""" - - @staticmethod - def from_keplerian_apsis_altitude(apo_alt_km: float, peri_alt_km: float, inc_deg: float, raan_deg: float, aop_deg: float, ta_deg: float, epoch: Epoch, frame: Frame) -> Orbit: - """Creates a new Orbit from the provided altitudes of apoapsis and periapsis, in kilometers""" - - @staticmethod - def from_keplerian_apsis_radii(r_a_km: float, r_p_km: float, inc_deg: float, raan_deg: float, aop_deg: float, ta_deg: float, epoch: Epoch, frame: Frame) -> Orbit: - """Attempts to create a new Orbit from the provided radii of apoapsis and periapsis, in kilometers""" - - @staticmethod - def from_keplerian_mean_anomaly(sma_km: float, ecc: float, inc_deg: float, raan_deg: float, aop_deg: float, ma_deg: float, epoch: Epoch, frame: Frame) -> Orbit: - """Initializes a new orbit from the Keplerian orbital elements using the mean anomaly instead of the true anomaly. - -# Implementation notes -This function starts by converting the mean anomaly to true anomaly, and then it initializes the orbit -using the keplerian(..) method. -The conversion is from GMAT's MeanToTrueAnomaly function, transliterated originally by Claude and GPT4 with human adjustments.""" - - @staticmethod - def from_latlongalt(latitude_deg: float, longitude_deg: float, height_km: float, angular_velocity: float, epoch: Epoch, frame: Frame) -> Orbit: - """Creates a new Orbit from the latitude (φ), longitude (λ) and height (in km) with respect to the frame's ellipsoid given the angular velocity. - -**Units:** degrees, degrees, km, rad/s -NOTE: This computation differs from the spherical coordinates because we consider the flattening of body. -Reference: G. Xu and Y. Xu, "GPS", DOI 10.1007/978-3-662-50367-6_2, 2016""" - - def height_km(self) -> float: - """Returns the geodetic height in km. - -Reference: Vallado, 4th Ed., Algorithm 12 page 172.""" - - def hmag(self) -> float: - """Returns the norm of the orbital momentum""" - - def hx(self) -> float: - """Returns the orbital momentum value on the X axis""" - - def hy(self) -> float: - """Returns the orbital momentum value on the Y axis""" - - def hyperbolic_anomaly_deg(self) -> float: - """Returns the hyperbolic anomaly in degrees between 0 and 360.0 -Returns an error if the orbit is not hyperbolic.""" - - def hz(self) -> float: - """Returns the orbital momentum value on the Z axis""" - - def inc_deg(self) -> float: - """Returns the inclination in degrees""" - - def is_brouwer_short_valid(self) -> bool: - """Returns whether this state satisfies the requirement to compute the Mean Brouwer Short orbital -element set. - -This is a conversion from GMAT's StateConversionUtil::CartesianToBrouwerMeanShort. -The details are at the log level `info`. -NOTE: Mean Brouwer Short are only defined around Earth. However, `nyx` does *not* check the -main celestial body around which the state is defined (GMAT does perform this verification).""" - - def latitude_deg(self) -> float: - """Returns the geodetic latitude (φ) in degrees. Value is between -180 and +180 degrees. - -# Frame warning -This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**.""" - - def latlongalt(self) -> typing.Tuple: - """Returns the geodetic latitude, geodetic longitude, and geodetic height, respectively in degrees, degrees, and kilometers. - -# Algorithm -This uses the Heikkinen procedure, which is not iterative. The results match Vallado and GMAT.""" - - def light_time(self) -> Duration: - """Returns the light time duration between this object and the origin of its reference frame.""" - - def longitude_360_deg(self) -> float: - """Returns the geodetic longitude (λ) in degrees. Value is between 0 and 360 degrees. - -# Frame warning -This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**.""" - - def longitude_deg(self) -> float: - """Returns the geodetic longitude (λ) in degrees. Value is between -180 and 180 degrees. - -# Frame warning -This state MUST be in the body fixed frame (e.g. ITRF93) prior to calling this function, or the computation is **invalid**.""" - - def ma_deg(self) -> float: - """Returns the mean anomaly in degrees - -This is a conversion from GMAT's StateConversionUtil::TrueToMeanAnomaly""" - - def periapsis_altitude_km(self) -> float: - """Returns the altitude of periapsis (or perigee around Earth), in kilometers.""" - - def periapsis_km(self) -> float: - """Returns the radius of periapsis (or perigee around Earth), in kilometers.""" - - def period(self) -> Duration: - """Returns the period in seconds""" - - def raan_deg(self) -> float: - """Returns the right ascension of the ascending node in degrees""" - - def rel_difference(self, other: Orbit) -> typing.Tuple: - """Returns the relative difference between this orbit and another for the position and velocity, respectively the first and second return values. -Both return values are UNITLESS because the relative difference is computed as the absolute difference divided by the rmag and vmag of this object. -Raises an error if the frames do not match, if the position is zero or the velocity is zero.""" - - def rel_pos_diff(self, other: Orbit) -> float: - """Returns the relative position difference (unitless) between this orbit and another. -This is computed by dividing the absolute difference by the norm of this object's radius vector. -If the radius is zero, this function raises a math error. -Raises an error if the frames do not match or (epochs do not need to match).""" - - def rel_vel_diff(self, other: Orbit) -> float: - """Returns the absolute velocity difference in kilometer per second between this orbit and another. -Raises an error if the frames do not match (epochs do not need to match).""" - - def ric_difference(self, other: Orbit) -> Orbit: - """Returns a Cartesian state representing the RIC difference between self and other, in position and velocity (with transport theorem). -Refer to dcm_from_ric_to_inertial for details on the RIC frame. - -# Algorithm -1. Compute the RIC DCM of self -2. Rotate self into the RIC frame -3. Rotation other into the RIC frame -4. Compute the difference between these two states -5. Strip the astrodynamical information from the frame, enabling only computations from `CartesianState`""" - - def right_ascension_deg(self) -> float: - """Returns the right ascension of this orbit in degrees""" - - def rmag_km(self) -> float: - """Returns the magnitude of the radius vector in km""" - - def rms_radius_km(self, other: Orbit) -> float: - """Returns the root sum squared (RMS) radius difference between this state and another state, if both frames match (epoch does not need to match)""" - - def rms_velocity_km_s(self, other: Orbit) -> float: - """Returns the root sum squared (RMS) velocity difference between this state and another state, if both frames match (epoch does not need to match)""" - - def rss_radius_km(self, other: Orbit) -> float: - """Returns the root mean squared (RSS) radius difference between this state and another state, if both frames match (epoch does not need to match)""" - - def rss_velocity_km_s(self, other: Orbit) -> float: - """Returns the root mean squared (RSS) velocity difference between this state and another state, if both frames match (epoch does not need to match)""" - - def semi_minor_axis_km(self) -> float: - """Returns the semi minor axis in km, includes code for a hyperbolic orbit""" - - def semi_parameter_km(self) -> float: - """Returns the semi parameter (or semilatus rectum)""" - - def set_aop_deg(self, new_aop_deg: float) -> None: - """Mutates this orbit to change the AOP""" - - def set_ecc(self, new_ecc: float) -> None: - """Mutates this orbit to change the ECC""" - - def set_inc_deg(self, new_inc_deg: float) -> None: - """Mutates this orbit to change the INC""" - - def set_raan_deg(self, new_raan_deg: float) -> None: - """Mutates this orbit to change the RAAN""" - - def set_sma_km(self, new_sma_km: float) -> None: - """Mutates this orbit to change the SMA""" - - def set_ta_deg(self, new_ta_deg: float) -> None: - """Mutates this orbit to change the TA""" - - def sma_altitude_km(self) -> float: - """Returns the SMA altitude in km""" - - def sma_km(self) -> float: - """Returns the semi-major axis in km""" - - def ta_deg(self) -> float: - """Returns the true anomaly in degrees between 0 and 360.0 - -NOTE: This function will emit a warning stating that the TA should be avoided if in a very near circular orbit -Code from - -LIMITATION: For an orbit whose true anomaly is (very nearly) 0.0 or 180.0, this function may return either 0.0 or 180.0 with a very small time increment. -This is due to the precision of the cosine calculation: if the arccosine calculation is out of bounds, the sign of the cosine of the true anomaly is used -to determine whether the true anomaly should be 0.0 or 180.0. **In other words**, there is an ambiguity in the computation in the true anomaly exactly at 180.0 and 0.0.""" - - def ta_dot_deg_s(self) -> float: - """Returns the time derivative of the true anomaly computed as the 360.0 degrees divided by the orbital period (in seconds).""" - - def tlong_deg(self) -> float: - """Returns the true longitude in degrees""" - - def velocity_declination_deg(self) -> float: - """Returns the velocity declination of this orbit in degrees""" - - def vinf_periapsis_km(self, turn_angle_degrees: float) -> float: - """Returns the radius of periapse in kilometers for the provided turn angle of this hyperbolic orbit. -Returns an error if the orbit is not hyperbolic.""" - - def vinf_turn_angle_deg(self, periapsis_km: float) -> float: - """Returns the turn angle in degrees for the provided radius of periapse passage of this hyperbolic orbit -Returns an error if the orbit is not hyperbolic.""" - - def vmag_km_s(self) -> float: - """Returns the magnitude of the velocity vector in km/s""" - - def vnc_difference(self, other: Orbit) -> Orbit: - """Returns a Cartesian state representing the VNC difference between self and other, in position and velocity (with transport theorem). -Refer to dcm_from_vnc_to_inertial for details on the VNC frame. - -# Algorithm -1. Compute the VNC DCM of self -2. Rotate self into the VNC frame -3. Rotation other into the VNC frame -4. Compute the difference between these two states -5. Strip the astrodynamical information from the frame, enabling only computations from `CartesianState`""" - - def with_aop_deg(self, new_aop_deg: float) -> Orbit: - """Returns a copy of the state with a new AOP""" - - def with_apoapsis_periapsis_km(self, new_ra_km: float, new_rp_km: float) -> Orbit: - """Returns a copy of this state with the provided apoasis and periapsis""" - - def with_ecc(self, new_ecc: float) -> Orbit: - """Returns a copy of the state with a new ECC""" - - def with_inc_deg(self, new_inc_deg: float) -> Orbit: - """Returns a copy of the state with a new INC""" - - def with_raan_deg(self, new_raan_deg: float) -> Orbit: - """Returns a copy of the state with a new RAAN""" - - def with_sma_km(self, new_sma_km: float) -> Orbit: - """Returns a copy of the state with a new SMA""" - - def with_ta_deg(self, new_ta_deg: float) -> Orbit: - """Returns a copy of the state with a new TA""" - - def __eq__(self, value: typing.Any) -> bool: - """Return self==value.""" - - def __ge__(self, value: typing.Any) -> bool: - """Return self>=value.""" - - def __getnewargs__(self) -> typing.Tuple:... - - def __gt__(self, value: typing.Any) -> bool: - """Return self>value.""" - - def __le__(self, value: typing.Any) -> bool: - """Return self<=value.""" - - def __lt__(self, value: typing.Any) -> bool: - """Return self bool: - """Return self!=value.""" - - def __repr__(self) -> str: - """Return repr(self).""" - - def __str__(self) -> str: - """Return str(self).""" \ No newline at end of file diff --git a/anise-py/anise.pyi b/anise-py/anise.pyi index 429e18a1..f46cbba9 100644 --- a/anise-py/anise.pyi +++ b/anise-py/anise.pyi @@ -1,5 +1,7 @@ import typing +_all__: list = ["time", "astro", "utils", "Aberration", "Almanac", "MetaAlmanac", "MetaFile"] + @typing.final class Aberration: """Represents the aberration correction options in ANISE. @@ -404,6 +406,8 @@ This function modified `self` and changes the URI to be the path to the download @typing.final class astro: + _all__: list = ["constants", "AzElRange", "Ellipsoid", "Occultation", "Orbit"] + @typing.final class AzElRange: """A structure that stores the result of Azimuth, Elevation, Range, Range rate calculation.""" diff --git a/anise-py/anise.time.pyi b/anise-py/anise.time.pyi deleted file mode 100644 index e69de29b..00000000 diff --git a/anise-py/anise.utils.pyi b/anise-py/anise.utils.pyi deleted file mode 100644 index 3f218614..00000000 --- a/anise-py/anise.utils.pyi +++ /dev/null @@ -1,9 +0,0 @@ -import typing - -def convert_fk(fk_file_path: str, anise_output_path: str, show_comments: bool=None, overwrite: bool=None) -> None: - """Converts a KPL/FK file, that defines frame constants like fixed rotations, and frame name to ID mappings into the EulerParameterDataSet equivalent ANISE file. -KPL/FK files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE.""" - -def convert_tpc(pck_file_path: str, gm_file_path: str, anise_output_path: str, overwrite: bool=None) -> None: - """Converts two KPL/TPC files, one defining the planetary constants as text, and the other defining the gravity parameters, into the PlanetaryDataSet equivalent ANISE file. -KPL/TPC files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE.""" \ No newline at end of file diff --git a/anise-py/hifitime.pyi b/anise-py/hifitime.pyi deleted file mode 100644 index 537676c2..00000000 --- a/anise-py/hifitime.pyi +++ /dev/null @@ -1,1087 +0,0 @@ -import typing - -@typing.final -class Duration: - """Defines generally usable durations for nanosecond precision valid for 32,768 centuries in either direction, and only on 80 bits / 10 octets. - -**Important conventions:** -1. The negative durations can be mentally modeled "BC" years. One hours before 01 Jan 0000, it was "-1" years but 365 days and 23h into the current day. -It was decided that the nanoseconds corresponds to the nanoseconds _into_ the current century. In other words, -a duration with centuries = -1 and nanoseconds = 0 is _a greater duration_ (further from zero) than centuries = -1 and nanoseconds = 1. -Duration zero minus one nanosecond returns a century of -1 and a nanosecond set to the number of nanoseconds in one century minus one. -That difference is exactly 1 nanoseconds, where the former duration is "closer to zero" than the latter. -As such, the largest negative duration that can be represented sets the centuries to i16::MAX and its nanoseconds to NANOSECONDS_PER_CENTURY. -2. It was also decided that opposite durations are equal, e.g. -15 minutes == 15 minutes. If the direction of time matters, use the signum function. - -(Python documentation hints)""" - - def __init__(self, string_repr: str) -> Duration: - """Defines generally usable durations for nanosecond precision valid for 32,768 centuries in either direction, and only on 80 bits / 10 octets. - -**Important conventions:** -1. The negative durations can be mentally modeled "BC" years. One hours before 01 Jan 0000, it was "-1" years but 365 days and 23h into the current day. -It was decided that the nanoseconds corresponds to the nanoseconds _into_ the current century. In other words, -a duration with centuries = -1 and nanoseconds = 0 is _a greater duration_ (further from zero) than centuries = -1 and nanoseconds = 1. -Duration zero minus one nanosecond returns a century of -1 and a nanosecond set to the number of nanoseconds in one century minus one. -That difference is exactly 1 nanoseconds, where the former duration is "closer to zero" than the latter. -As such, the largest negative duration that can be represented sets the centuries to i16::MAX and its nanoseconds to NANOSECONDS_PER_CENTURY. -2. It was also decided that opposite durations are equal, e.g. -15 minutes == 15 minutes. If the direction of time matters, use the signum function. - -(Python documentation hints)""" - - @staticmethod - def EPSILON():... - - @staticmethod - def MAX():... - - @staticmethod - def MIN():... - - @staticmethod - def MIN_NEGATIVE():... - - @staticmethod - def MIN_POSITIVE():... - - @staticmethod - def ZERO():... - - def abs(self) -> Duration: - """Returns the absolute value of this duration""" - - def approx(self) -> Duration: - """Rounds this duration to the largest units represented in this duration. - -This is useful to provide an approximate human duration. Under the hood, this function uses `round`, -so the "tipping point" of the rounding is half way to the next increment of the greatest unit. -As shown below, one example is that 35 hours and 59 minutes rounds to 1 day, but 36 hours and 1 minute rounds -to 2 days because 2 days is closer to 36h 1 min than 36h 1 min is to 1 day. - -# Example - -``` -use hifitime::{Duration, TimeUnits}; - -assert_eq!((2.hours() + 3.minutes()).approx(), 2.hours()); -assert_eq!((24.hours() + 3.minutes()).approx(), 1.days()); -assert_eq!((35.hours() + 59.minutes()).approx(), 1.days()); -assert_eq!((36.hours() + 1.minutes()).approx(), 2.days()); -assert_eq!((47.hours() + 3.minutes()).approx(), 2.days()); -assert_eq!((49.hours() + 3.minutes()).approx(), 2.days()); -```""" - - def ceil(self, duration: Duration) -> Duration: - """Ceils this duration to the closest provided duration - -This simply floors then adds the requested duration - -# Example -``` -use hifitime::{Duration, TimeUnits}; - -let two_hours_three_min = 2.hours() + 3.minutes(); -assert_eq!(two_hours_three_min.ceil(1.hours()), 3.hours()); -assert_eq!(two_hours_three_min.ceil(30.minutes()), 2.hours() + 30.minutes()); -assert_eq!(two_hours_three_min.ceil(4.hours()), 4.hours()); -assert_eq!(two_hours_three_min.ceil(1.seconds()), two_hours_three_min + 1.seconds()); -assert_eq!(two_hours_three_min.ceil(1.hours() + 5.minutes()), 2.hours() + 10.minutes()); -```""" - - def decompose(self) -> typing.Tuple: - """Decomposes a Duration in its sign, days, hours, minutes, seconds, ms, us, ns""" - - def floor(self, duration: Duration) -> Duration: - """Floors this duration to the closest duration from the bottom - -# Example -``` -use hifitime::{Duration, TimeUnits}; - -let two_hours_three_min = 2.hours() + 3.minutes(); -assert_eq!(two_hours_three_min.floor(1.hours()), 2.hours()); -assert_eq!(two_hours_three_min.floor(30.minutes()), 2.hours()); -// This is zero because we floor by a duration longer than the current duration, rounding it down -assert_eq!(two_hours_three_min.floor(4.hours()), 0.hours()); -assert_eq!(two_hours_three_min.floor(1.seconds()), two_hours_three_min); -assert_eq!(two_hours_three_min.floor(1.hours() + 1.minutes()), 2.hours() + 2.minutes()); -assert_eq!(two_hours_three_min.floor(1.hours() + 5.minutes()), 1.hours() + 5.minutes()); -```""" - - @staticmethod - def from_all_parts(sign: int, days: int, hours: int, minutes: int, seconds: int, milliseconds: int, microseconds: int, nanoseconds: int) -> Duration: - """Creates a new duration from its parts""" - - @staticmethod - def from_parts(centuries: int, nanoseconds: int) -> Duration: - """Create a normalized duration from its parts""" - - @staticmethod - def from_total_nanoseconds(nanos: int) -> Duration: - """Creates a new Duration from its full nanoseconds""" - - def is_negative(self) -> bool: - """Returns whether this is a negative or positive duration.""" - - def max(self, other: Duration) -> Duration: - """Returns the maximum of the two durations. - -``` -use hifitime::TimeUnits; - -let d0 = 20.seconds(); -let d1 = 21.seconds(); - -assert_eq!(d1, d1.max(d0)); -assert_eq!(d1, d0.max(d1)); -```""" - - def min(self, other: Duration) -> Duration: - """Returns the minimum of the two durations. - -``` -use hifitime::TimeUnits; - -let d0 = 20.seconds(); -let d1 = 21.seconds(); - -assert_eq!(d0, d1.min(d0)); -assert_eq!(d0, d0.min(d1)); -```""" - - def round(self, duration: Duration) -> Duration: - """Rounds this duration to the closest provided duration - -This performs both a `ceil` and `floor` and returns the value which is the closest to current one. -# Example -``` -use hifitime::{Duration, TimeUnits}; - -let two_hours_three_min = 2.hours() + 3.minutes(); -assert_eq!(two_hours_three_min.round(1.hours()), 2.hours()); -assert_eq!(two_hours_three_min.round(30.minutes()), 2.hours()); -assert_eq!(two_hours_three_min.round(4.hours()), 4.hours()); -assert_eq!(two_hours_three_min.round(1.seconds()), two_hours_three_min); -assert_eq!(two_hours_three_min.round(1.hours() + 5.minutes()), 2.hours() + 10.minutes()); -```""" - - def signum(self) -> int: - """Returns the sign of this duration -+ 0 if the number is zero -+ 1 if the number is positive -+ -1 if the number is negative""" - - def to_parts(self) -> typing.Tuple: - """Returns the centuries and nanoseconds of this duration -NOTE: These items are not public to prevent incorrect durations from being created by modifying the values of the structure directly.""" - - def to_seconds(self) -> float: - """Returns this duration in seconds f64. -For high fidelity comparisons, it is recommended to keep using the Duration structure.""" - - def to_unit(self, unit: Unit) -> float:... - - def total_nanoseconds(self) -> int: - """Returns the total nanoseconds in a signed 128 bit integer""" - - def __add__(): - """Return self+value.""" - - def __div__():... - - def __eq__(self, value: typing.Any) -> bool: - """Return self==value.""" - - def __ge__(self, value: typing.Any) -> bool: - """Return self>=value.""" - - def __getnewargs__(self):... - - def __gt__(self, value: typing.Any) -> bool: - """Return self>value.""" - - def __le__(self, value: typing.Any) -> bool: - """Return self<=value.""" - - def __lt__(self, value: typing.Any) -> bool: - """Return self bool: - """Return self!=value.""" - - def __radd__(): - """Return value+self.""" - - def __repr__(self) -> str: - """Return repr(self).""" - - def __rmul__(): - """Return value*self.""" - - def __rsub__(): - """Return value-self.""" - - def __str__(self) -> str: - """Return str(self).""" - - def __sub__(): - """Return self-value.""" - -@typing.final -class DurationError: - __cause__: typing.Any - __context__: typing.Any - __suppress_context__: typing.Any - __traceback__: typing.Any - args: typing.Any - - def add_note(): - """Exception.add_note(note) -- -add a note to the exception""" - - def with_traceback(): - """Exception.with_traceback(tb) -- -set self.__traceback__ to tb and return self.""" - - def __delattr__(): - """Implement delattr(self, name).""" - - def __getattribute__(): - """Return getattr(self, name).""" - - def __init__(): - """Initialize self. See help(type(self)) for accurate signature.""" - - def __repr__(): - """Return repr(self).""" - - def __setattr__(): - """Implement setattr(self, name, value).""" - - def __setstate__():... - - def __str__(): - """Return str(self).""" - -@typing.final -class Epoch: - """Defines a nanosecond-precision Epoch. - -Refer to the appropriate functions for initializing this Epoch from different time scales or representations. - -(Python documentation hints)""" - - def __init__(self, string_repr: str) -> Epoch: - """Defines a nanosecond-precision Epoch. - -Refer to the appropriate functions for initializing this Epoch from different time scales or representations. - -(Python documentation hints)""" - - def day_of_year(self) -> float: - """Returns the number of days since the start of the year.""" - - def duration_in_year(self) -> Duration: - """Returns the duration since the start of the year""" - - def hours(self) -> int: - """Returns the hours of the Gregorian representation of this epoch in the time scale it was initialized in.""" - - @staticmethod - def init_from_bdt_days(days: float) -> Epoch: - """Initialize an Epoch from the number of days since the BeiDou Time Epoch, -defined as January 1st 2006 (cf. ).""" - - @staticmethod - def init_from_bdt_nanoseconds(nanoseconds: float) -> Epoch: - """Initialize an Epoch from the number of days since the BeiDou Time Epoch, -defined as January 1st 2006 (cf. ). -This may be useful for time keeping devices that use BDT as a time source.""" - - @staticmethod - def init_from_bdt_seconds(seconds: float) -> Epoch: - """Initialize an Epoch from the number of seconds since the BeiDou Time Epoch, -defined as January 1st 2006 (cf. ).""" - - @staticmethod - def init_from_et_duration(duration_since_j2000: Duration) -> Epoch: - """Initialize an Epoch from the Ephemeris Time duration past 2000 JAN 01 (J2000 reference)""" - - @staticmethod - def init_from_et_seconds(seconds_since_j2000: float) -> Epoch: - """Initialize an Epoch from the Ephemeris Time seconds past 2000 JAN 01 (J2000 reference)""" - - @staticmethod - def init_from_gpst_days(days: float) -> Epoch: - """Initialize an Epoch from the number of days since the GPS Time Epoch, -defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" - - @staticmethod - def init_from_gpst_nanoseconds(nanoseconds: float) -> Epoch: - """Initialize an Epoch from the number of nanoseconds since the GPS Time Epoch, -defined as UTC midnight of January 5th to 6th 1980 (cf. ). -This may be useful for time keeping devices that use GPS as a time source.""" - - @staticmethod - def init_from_gpst_seconds(seconds: float) -> Epoch: - """Initialize an Epoch from the number of seconds since the GPS Time Epoch, -defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" - - @staticmethod - def init_from_gregorian(year: int, month: int, day: int, hour: int, minute: int, second: int, nanos: int, time_scale: TimeScale) -> Epoch: - """Initialize from the Gregorian parts""" - - @staticmethod - def init_from_gregorian_at_midnight(year: int, month: int, day: int, time_scale: TimeScale) -> Epoch: - """Initialize from the Gregorian parts, time set to midnight""" - - @staticmethod - def init_from_gregorian_at_noon(year: int, month: int, day: int, time_scale: TimeScale) -> Epoch: - """Initialize from the Gregorian parts, time set to noon""" - - @staticmethod - def init_from_gregorian_utc(year: int, month: int, day: int, hour: int, minute: int, second: int, nanos: int) -> Epoch: - """Builds an Epoch from the provided Gregorian date and time in TAI. If invalid date is provided, this function will panic. -Use maybe_from_gregorian_tai if unsure.""" - - @staticmethod - def init_from_gst_days(days: float) -> Epoch: - """Initialize an Epoch from the number of days since the Galileo Time Epoch, -starting on August 21st 1999 Midnight UT, -(cf. ).""" - - @staticmethod - def init_from_gst_nanoseconds(nanoseconds: float) -> Epoch: - """Initialize an Epoch from the number of nanoseconds since the Galileo Time Epoch, -starting on August 21st 1999 Midnight UT, -(cf. ). -This may be useful for time keeping devices that use GST as a time source.""" - - @staticmethod - def init_from_gst_seconds(seconds: float) -> Epoch: - """Initialize an Epoch from the number of seconds since the Galileo Time Epoch, -starting on August 21st 1999 Midnight UT, -(cf. ).""" - - @staticmethod - def init_from_jde_et(days: float) -> Epoch: - """Initialize from the JDE days""" - - @staticmethod - def init_from_jde_tai(days: float) -> Epoch: - """Initialize an Epoch from given JDE in TAI time scale""" - - @staticmethod - def init_from_jde_tdb(days: float) -> Epoch: - """Initialize from Dynamic Barycentric Time (TDB) (same as SPICE ephemeris time) in JD days""" - - @staticmethod - def init_from_jde_utc(days: float) -> Epoch: - """Initialize an Epoch from given JDE in UTC time scale""" - - @staticmethod - def init_from_mjd_tai(days: float) -> Epoch: - """Initialize an Epoch from given MJD in TAI time scale""" - - @staticmethod - def init_from_mjd_utc(days: float) -> Epoch: - """Initialize an Epoch from given MJD in UTC time scale""" - - @staticmethod - def init_from_qzsst_days(days: float) -> Epoch: - """Initialize an Epoch from the number of days since the QZSS Time Epoch, -defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" - - @staticmethod - def init_from_qzsst_nanoseconds(nanoseconds: int) -> Epoch: - """Initialize an Epoch from the number of nanoseconds since the QZSS Time Epoch, -defined as UTC midnight of January 5th to 6th 1980 (cf. ). -This may be useful for time keeping devices that use QZSS as a time source.""" - - @staticmethod - def init_from_qzsst_seconds(seconds: float) -> Epoch: - """Initialize an Epoch from the number of seconds since the QZSS Time Epoch, -defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" - - @staticmethod - def init_from_tai_days(days: float) -> Epoch: - """Initialize an Epoch from the provided TAI days since 1900 January 01 at midnight""" - - @staticmethod - def init_from_tai_duration(duration: Duration) -> Epoch: - """Creates a new Epoch from a Duration as the time difference between this epoch and TAI reference epoch.""" - - @staticmethod - def init_from_tai_parts(centuries: int, nanoseconds: int) -> Epoch: - """Creates a new Epoch from its centuries and nanosecond since the TAI reference epoch.""" - - @staticmethod - def init_from_tai_seconds(seconds: float) -> Epoch: - """Initialize an Epoch from the provided TAI seconds since 1900 January 01 at midnight""" - - @staticmethod - def init_from_tdb_duration(duration_since_j2000: Duration) -> Epoch: - """Initialize from Dynamic Barycentric Time (TDB) (same as SPICE ephemeris time) whose epoch is 2000 JAN 01 noon TAI.""" - - @staticmethod - def init_from_tdb_seconds(seconds_j2000: float) -> Epoch: - """Initialize an Epoch from Dynamic Barycentric Time (TDB) seconds past 2000 JAN 01 midnight (difference than SPICE) -NOTE: This uses the ESA algorithm, which is a notch more complicated than the SPICE algorithm, but more precise. -In fact, SPICE algorithm is precise +/- 30 microseconds for a century whereas ESA algorithm should be exactly correct.""" - - @staticmethod - def init_from_tt_duration(duration: Duration) -> Epoch: - """Initialize an Epoch from the provided TT seconds (approximated to 32.184s delta from TAI)""" - - @staticmethod - def init_from_tt_seconds(seconds: float) -> Epoch: - """Initialize an Epoch from the provided TT seconds (approximated to 32.184s delta from TAI)""" - - @staticmethod - def init_from_unix_milliseconds(milliseconds: float) -> Epoch: - """Initialize an Epoch from the provided UNIX millisecond timestamp since UTC midnight 1970 January 01.""" - - @staticmethod - def init_from_unix_seconds(seconds: float) -> Epoch: - """Initialize an Epoch from the provided UNIX second timestamp since UTC midnight 1970 January 01.""" - - @staticmethod - def init_from_utc_days(days: float) -> Epoch: - """Initialize an Epoch from the provided UTC days since 1900 January 01 at midnight""" - - @staticmethod - def init_from_utc_seconds(seconds: float) -> Epoch: - """Initialize an Epoch from the provided UTC seconds since 1900 January 01 at midnight""" - - def isoformat(self) -> str: - """Equivalent to `datetime.isoformat`, and truncated to 23 chars, refer to for format options""" - - def leap_seconds(self, iers_only: bool) -> float: - """Get the accumulated number of leap seconds up to this Epoch accounting only for the IERS leap seconds and the SOFA scaling from 1960 to 1972, depending on flag. -Returns None if the epoch is before 1960, year at which UTC was defined. - -# Why does this function return an `Option` when the other returns a value -This is to match the `iauDat` function of SOFA (src/dat.c). That function will return a warning and give up if the start date is before 1960.""" - - def leap_seconds_iers(self) -> int: - """Get the accumulated number of leap seconds up to this Epoch accounting only for the IERS leap seconds.""" - - def leap_seconds_with_file(self, iers_only: bool, provider: LeapSecondsFile) -> float: - """Get the accumulated number of leap seconds up to this Epoch from the provided LeapSecondProvider. -Returns None if the epoch is before 1960, year at which UTC was defined. - -# Why does this function return an `Option` when the other returns a value -This is to match the `iauDat` function of SOFA (src/dat.c). That function will return a warning and give up if the start date is before 1960.""" - - def microseconds(self) -> int: - """Returns the microseconds of the Gregorian representation of this epoch in the time scale it was initialized in.""" - - def milliseconds(self) -> int: - """Returns the milliseconds of the Gregorian representation of this epoch in the time scale it was initialized in.""" - - def minutes(self) -> int: - """Returns the minutes of the Gregorian representation of this epoch in the time scale it was initialized in.""" - - def month_name(self) -> MonthName:... - - def nanoseconds(self) -> int: - """Returns the nanoseconds of the Gregorian representation of this epoch in the time scale it was initialized in.""" - - def seconds(self) -> int: - """Returns the seconds of the Gregorian representation of this epoch in the time scale it was initialized in.""" - - def strftime(self, format_str: str) -> str: - """Equivalent to `datetime.strftime`, refer to for format options""" - - @staticmethod - def strptime(epoch_str: str, format_str: str) -> Epoch: - """Equivalent to `datetime.strptime`, refer to for format options""" - - @staticmethod - def system_now() -> Epoch: - """Returns the computer clock in UTC""" - - def timedelta(self, other: Duration) -> Duration: - """Differences between two epochs""" - - def to_bdt_days(self) -> float: - """Returns days past BDT (BeiDou) Time Epoch, defined as Jan 01 2006 UTC -(cf. ).""" - - def to_bdt_duration(self) -> Duration: - """Returns `Duration` past BDT (BeiDou) time Epoch.""" - - def to_bdt_nanoseconds(self) -> int: - """Returns nanoseconds past BDT (BeiDou) Time Epoch, defined as Jan 01 2006 UTC -(cf. ). -NOTE: This function will return an error if the centuries past GST time are not zero.""" - - def to_bdt_seconds(self) -> float: - """Returns seconds past BDT (BeiDou) Time Epoch""" - - def to_duration_in_time_scale(self, ts: TimeScale) -> Duration: - """Returns this epoch with respect to the provided time scale. -This is needed to correctly perform duration conversions in dynamical time scales (e.g. TDB).""" - - def to_et_centuries_since_j2000(self) -> float: - """Returns the number of centuries since Ephemeris Time (ET) J2000 (used for Archinal et al. rotations)""" - - def to_et_days_since_j2000(self) -> float: - """Returns the number of days since Ephemeris Time (ET) J2000 (used for Archinal et al. rotations)""" - - def to_et_duration(self) -> Duration: - """Returns the duration between J2000 and the current epoch as per NAIF SPICE. - -# Warning -The et2utc function of NAIF SPICE will assume that there are 9 leap seconds before 01 JAN 1972, -as this date introduces 10 leap seconds. At the time of writing, this does _not_ seem to be in -line with IERS and the documentation in the leap seconds list. - -In order to match SPICE, the as_et_duration() function will manually get rid of that difference.""" - - def to_et_seconds(self) -> float: - """Returns the Ephemeris Time seconds past 2000 JAN 01 midnight, matches NASA/NAIF SPICE.""" - - def to_gpst_days(self) -> float: - """Returns days past GPS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" - - def to_gpst_duration(self) -> Duration: - """Returns `Duration` past GPS time Epoch.""" - - def to_gpst_nanoseconds(self) -> int: - """Returns nanoseconds past GPS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ). -NOTE: This function will return an error if the centuries past GPST time are not zero.""" - - def to_gpst_seconds(self) -> float: - """Returns seconds past GPS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" - - def to_gst_days(self) -> float: - """Returns days past GST (Galileo) Time Epoch, -starting on August 21st 1999 Midnight UT -(cf. ).""" - - def to_gst_duration(self) -> Duration: - """Returns `Duration` past GST (Galileo) time Epoch.""" - - def to_gst_nanoseconds(self) -> int: - """Returns nanoseconds past GST (Galileo) Time Epoch, starting on August 21st 1999 Midnight UT -(cf. ). -NOTE: This function will return an error if the centuries past GST time are not zero.""" - - def to_gst_seconds(self) -> float: - """Returns seconds past GST (Galileo) Time Epoch""" - - def to_isoformat(self) -> str: - """The standard ISO format of this epoch (six digits of subseconds) in the _current_ time scale, refer to for format options.""" - - def to_jde_et(self, unit: Unit) -> float:... - - def to_jde_et_days(self) -> float: - """Returns the Ephemeris Time JDE past epoch""" - - def to_jde_et_duration(self) -> Duration:... - - def to_jde_tai(self, unit: Unit) -> float: - """Returns the Julian Days from epoch 01 Jan -4713 12:00 (noon) in desired Duration::Unit""" - - def to_jde_tai_days(self) -> float: - """Returns the Julian days from epoch 01 Jan -4713, 12:00 (noon) -as explained in "Fundamentals of astrodynamics and applications", Vallado et al. -4th edition, page 182, and on [Wikipedia](https://en.wikipedia.org/wiki/Julian_day).""" - - def to_jde_tai_duration(self) -> Duration: - """Returns the Julian Days from epoch 01 Jan -4713 12:00 (noon) as a Duration""" - - def to_jde_tai_seconds(self) -> float: - """Returns the Julian seconds in TAI.""" - - def to_jde_tdb_days(self) -> float: - """Returns the Dynamic Barycentric Time (TDB) (higher fidelity SPICE ephemeris time) whose epoch is 2000 JAN 01 noon TAI (cf. )""" - - def to_jde_tdb_duration(self) -> Duration:... - - def to_jde_tt_days(self) -> float: - """Returns days past Julian epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT))""" - - def to_jde_tt_duration(self) -> Duration:... - - def to_jde_utc_days(self) -> float: - """Returns the Julian days in UTC.""" - - def to_jde_utc_duration(self) -> Duration: - """Returns the Julian days in UTC as a `Duration`""" - - def to_jde_utc_seconds(self) -> float: - """Returns the Julian Days in UTC seconds.""" - - def to_mjd_tai(self, unit: Unit) -> float: - """Returns this epoch as a duration in the requested units in MJD TAI""" - - def to_mjd_tai_days(self) -> float: - """`as_mjd_days` creates an Epoch from the provided Modified Julian Date in days as explained -[here](http://tycho.usno.navy.mil/mjd.html). MJD epoch is Modified Julian Day at 17 November 1858 at midnight.""" - - def to_mjd_tai_seconds(self) -> float: - """Returns the Modified Julian Date in seconds TAI.""" - - def to_mjd_tt_days(self) -> float: - """Returns days past Modified Julian epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT))""" - - def to_mjd_tt_duration(self) -> Duration:... - - def to_mjd_utc(self, unit: Unit) -> float: - """Returns the Modified Julian Date in the provided unit in UTC.""" - - def to_mjd_utc_days(self) -> float: - """Returns the Modified Julian Date in days UTC.""" - - def to_mjd_utc_seconds(self) -> float: - """Returns the Modified Julian Date in seconds UTC.""" - - def to_nanoseconds_in_time_scale(self, time_scale: TimeScale) -> int: - """Attempts to return the number of nanoseconds since the reference epoch of the provided time scale. -This will return an overflow error if more than one century has past since the reference epoch in the provided time scale. -If this is _not_ an issue, you should use `epoch.to_duration_in_time_scale().to_parts()` to retrieve both the centuries and the nanoseconds -in that century.""" - - def to_qzsst_days(self) -> float: - """Returns days past QZSS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" - - def to_qzsst_duration(self) -> Duration: - """Returns `Duration` past QZSS time Epoch.""" - - def to_qzsst_nanoseconds(self) -> int: - """Returns nanoseconds past QZSS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ). -NOTE: This function will return an error if the centuries past QZSST time are not zero.""" - - def to_qzsst_seconds(self) -> float: - """Returns seconds past QZSS Time Epoch, defined as UTC midnight of January 5th to 6th 1980 (cf. ).""" - - def to_rfc3339(self) -> str: - """Returns this epoch in UTC in the RFC3339 format""" - - def to_tai(self, unit: Unit) -> float: - """Returns the epoch as a floating point value in the provided unit""" - - def to_tai_days(self) -> float: - """Returns the number of days since J1900 in TAI""" - - def to_tai_duration(self) -> Duration: - """Returns this time in a Duration past J1900 counted in TAI""" - - def to_tai_parts(self) -> typing.Tuple: - """Returns the TAI parts of this duration""" - - def to_tai_seconds(self) -> float: - """Returns the number of TAI seconds since J1900""" - - def to_tdb_centuries_since_j2000(self) -> float: - """Returns the number of centuries since Dynamic Barycentric Time (TDB) J2000 (used for Archinal et al. rotations)""" - - def to_tdb_days_since_j2000(self) -> float: - """Returns the number of days since Dynamic Barycentric Time (TDB) J2000 (used for Archinal et al. rotations)""" - - def to_tdb_duration(self) -> Duration: - """Returns the Dynamics Barycentric Time (TDB) as a high precision Duration since J2000 - -## Algorithm -Given the embedded sine functions in the equation to compute the difference between TDB and TAI from the number of TDB seconds -past J2000, one cannot solve the revert the operation analytically. Instead, we iterate until the value no longer changes. - -1. Assume that the TAI duration is in fact the TDB seconds from J2000. -2. Offset to J2000 because `Epoch` stores everything in the J1900 but the TDB duration is in J2000. -3. Compute the offset `g` due to the TDB computation with the current value of the TDB seconds (defined in step 1). -4. Subtract that offset to the latest TDB seconds and store this as a new candidate for the true TDB seconds value. -5. Compute the difference between this candidate and the previous one. If the difference is less than one nanosecond, stop iteration. -6. Set the new candidate as the TDB seconds since J2000 and loop until step 5 breaks the loop, or we've done five iterations. -7. At this stage, we have a good approximation of the TDB seconds since J2000. -8. Reverse the algorithm given that approximation: compute the `g` offset, compute the difference between TDB and TAI, add the TT offset (32.184 s), and offset by the difference between J1900 and J2000.""" - - def to_tdb_seconds(self) -> float: - """Returns the Dynamic Barycentric Time (TDB) (higher fidelity SPICE ephemeris time) whose epoch is 2000 JAN 01 noon TAI (cf. )""" - - def to_time_scale(self, ts: TimeScale) -> Epoch: - """Converts self to another time scale - -As per the [Rust naming convention](https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv), -this borrows an Epoch and returns an owned Epoch.""" - - def to_tt_centuries_j2k(self) -> float: - """Returns the centuries passed J2000 TT""" - - def to_tt_days(self) -> float: - """Returns days past TAI epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT))""" - - def to_tt_duration(self) -> Duration: - """Returns `Duration` past TAI epoch in Terrestrial Time (TT).""" - - def to_tt_seconds(self) -> float: - """Returns seconds past TAI epoch in Terrestrial Time (TT) (previously called Terrestrial Dynamical Time (TDT))""" - - def to_tt_since_j2k(self) -> Duration: - """Returns the duration past J2000 TT""" - - def to_unix(self, unit: Unit) -> float: - """Returns the duration since the UNIX epoch in the provided unit.""" - - def to_unix_days(self) -> float: - """Returns the number days since the UNIX epoch defined 01 Jan 1970 midnight UTC.""" - - def to_unix_duration(self) -> Duration: - """Returns the Duration since the UNIX epoch UTC midnight 01 Jan 1970.""" - - def to_unix_milliseconds(self) -> float: - """Returns the number milliseconds since the UNIX epoch defined 01 Jan 1970 midnight UTC.""" - - def to_unix_seconds(self) -> float: - """Returns the number seconds since the UNIX epoch defined 01 Jan 1970 midnight UTC.""" - - def to_utc(self, unit: Unit) -> float: - """Returns the number of UTC seconds since the TAI epoch""" - - def to_utc_days(self) -> float: - """Returns the number of UTC days since the TAI epoch""" - - def to_utc_duration(self) -> Duration: - """Returns this time in a Duration past J1900 counted in UTC""" - - def to_utc_seconds(self) -> float: - """Returns the number of UTC seconds since the TAI epoch""" - - def year(self) -> int: - """Returns the number of Gregorian years of this epoch in the current time scale.""" - - def year_days_of_year(self) -> typing.Tuple: - """Returns the year and the days in the year so far (days of year).""" - - def __add__(): - """Return self+value.""" - - def __eq__(self, value: typing.Any) -> bool: - """Return self==value.""" - - def __ge__(self, value: typing.Any) -> bool: - """Return self>=value.""" - - def __getnewargs__(self):... - - def __gt__(self, value: typing.Any) -> bool: - """Return self>value.""" - - def __le__(self, value: typing.Any) -> bool: - """Return self<=value.""" - - def __lt__(self, value: typing.Any) -> bool: - """Return self bool: - """Return self!=value.""" - - def __radd__(): - """Return value+self.""" - - def __repr__(self) -> str: - """Return repr(self).""" - - def __rsub__(): - """Return value-self.""" - - def __str__(self) -> str: - """Return str(self).""" - - def __sub__(): - """Return self-value.""" - -@typing.final -class HifitimeError: - __cause__: typing.Any - __context__: typing.Any - __suppress_context__: typing.Any - __traceback__: typing.Any - args: typing.Any - - def add_note(): - """Exception.add_note(note) -- -add a note to the exception""" - - def with_traceback(): - """Exception.with_traceback(tb) -- -set self.__traceback__ to tb and return self.""" - - def __delattr__(): - """Implement delattr(self, name).""" - - def __getattribute__(): - """Return getattr(self, name).""" - - def __init__(): - """Initialize self. See help(type(self)) for accurate signature.""" - - def __repr__(): - """Return repr(self).""" - - def __setattr__(): - """Implement setattr(self, name, value).""" - - def __setstate__():... - - def __str__(): - """Return str(self).""" - -@typing.final -class LatestLeapSeconds: - """List of leap seconds from https://www.ietf.org/timezones/data/leap-seconds.list . -This list corresponds the number of seconds in TAI to the UTC offset and to whether it was an announced leap second or not. -The unannoucned leap seconds come from dat.c in the SOFA library.""" - - def __init__(self) -> None: - """List of leap seconds from https://www.ietf.org/timezones/data/leap-seconds.list . -This list corresponds the number of seconds in TAI to the UTC offset and to whether it was an announced leap second or not. -The unannoucned leap seconds come from dat.c in the SOFA library.""" - - def __repr__(self) -> str: - """Return repr(self).""" - -@typing.final -class LeapSecondsFile: - """A leap second provider that uses an IERS formatted leap seconds file. - -(Python documentation hints)""" - - def __init__(self, path: str) -> LeapSecondsFile: - """A leap second provider that uses an IERS formatted leap seconds file. - -(Python documentation hints)""" - - def __repr__(self) -> str: - """Return repr(self).""" - -@typing.final -class MonthName: - - def __eq__(self, value: typing.Any) -> bool: - """Return self==value.""" - - def __ge__(self, value: typing.Any) -> bool: - """Return self>=value.""" - - def __gt__(self, value: typing.Any) -> bool: - """Return self>value.""" - - def __int__(self) -> None: - """int(self)""" - - def __le__(self, value: typing.Any) -> bool: - """Return self<=value.""" - - def __lt__(self, value: typing.Any) -> bool: - """Return self bool: - """Return self!=value.""" - - def __repr__(self) -> str: - """Return repr(self).""" - April: MonthName = ... - August: MonthName = ... - December: MonthName = ... - February: MonthName = ... - January: MonthName = ... - July: MonthName = ... - June: MonthName = ... - March: MonthName = ... - May: MonthName = ... - November: MonthName = ... - October: MonthName = ... - September: MonthName = ... - -@typing.final -class ParsingError: - __cause__: typing.Any - __context__: typing.Any - __suppress_context__: typing.Any - __traceback__: typing.Any - args: typing.Any - - def add_note(): - """Exception.add_note(note) -- -add a note to the exception""" - - def with_traceback(): - """Exception.with_traceback(tb) -- -set self.__traceback__ to tb and return self.""" - - def __delattr__(): - """Implement delattr(self, name).""" - - def __getattribute__(): - """Return getattr(self, name).""" - - def __init__(): - """Initialize self. See help(type(self)) for accurate signature.""" - - def __repr__(): - """Return repr(self).""" - - def __setattr__(): - """Implement setattr(self, name, value).""" - - def __setstate__():... - - def __str__(): - """Return str(self).""" - -@typing.final -class TimeScale: - """Enum of the different time systems available""" - - def uses_leap_seconds(self) -> bool: - """Returns true if self takes leap seconds into account""" - - def __eq__(self, value: typing.Any) -> bool: - """Return self==value.""" - - def __ge__(self, value: typing.Any) -> bool: - """Return self>=value.""" - - def __gt__(self, value: typing.Any) -> bool: - """Return self>value.""" - - def __int__(self) -> None: - """int(self)""" - - def __le__(self, value: typing.Any) -> bool: - """Return self<=value.""" - - def __lt__(self, value: typing.Any) -> bool: - """Return self bool: - """Return self!=value.""" - - def __repr__(self) -> str: - """Return repr(self).""" - BDT: TimeScale = ... - ET: TimeScale = ... - GPST: TimeScale = ... - GST: TimeScale = ... - QZSST: TimeScale = ... - TAI: TimeScale = ... - TDB: TimeScale = ... - TT: TimeScale = ... - UTC: TimeScale = ... - -@typing.final -class TimeSeries: - """An iterator of a sequence of evenly spaced Epochs. - -(Python documentation hints)""" - - def __init__(self, start: Epoch, end: Epoch, step: Duration, inclusive: bool) -> TimeSeries: - """An iterator of a sequence of evenly spaced Epochs. - -(Python documentation hints)""" - - def __eq__(self, value: typing.Any) -> bool: - """Return self==value.""" - - def __ge__(self, value: typing.Any) -> bool: - """Return self>=value.""" - - def __getnewargs__(self):... - - def __gt__(self, value: typing.Any) -> bool: - """Return self>value.""" - - def __iter__(self) -> typing.Any: - """Implement iter(self).""" - - def __le__(self, value: typing.Any) -> bool: - """Return self<=value.""" - - def __lt__(self, value: typing.Any) -> bool: - """Return self bool: - """Return self!=value.""" - - def __next__(self) -> typing.Any: - """Implement next(self).""" - - def __repr__(self) -> str: - """Return repr(self).""" - - def __str__(self) -> str: - """Return str(self).""" - -@typing.final -class Unit: - """An Enum to perform time unit conversions.""" - - def from_seconds(self):... - - def in_seconds(self):... - - def __add__(): - """Return self+value.""" - - def __eq__(self, value: typing.Any) -> bool: - """Return self==value.""" - - def __ge__(self, value: typing.Any) -> bool: - """Return self>=value.""" - - def __gt__(self, value: typing.Any) -> bool: - """Return self>value.""" - - def __int__(self) -> None: - """int(self)""" - - def __le__(self, value: typing.Any) -> bool: - """Return self<=value.""" - - def __lt__(self, value: typing.Any) -> bool: - """Return self bool: - """Return self!=value.""" - - def __radd__(): - """Return value+self.""" - - def __repr__(self) -> str: - """Return repr(self).""" - - def __rmul__(): - """Return value*self.""" - - def __rsub__(): - """Return value-self.""" - - def __sub__(): - """Return self-value.""" - Century: Unit = ... - Day: Unit = ... - Hour: Unit = ... - Microsecond: Unit = ... - Millisecond: Unit = ... - Minute: Unit = ... - Nanosecond: Unit = ... - Second: Unit = ... - Week: Unit = ... - -@typing.final -class Ut1Provider: - """A structure storing all of the TAI-UT1 data""" - - def __init__(self) -> None: - """A structure storing all of the TAI-UT1 data""" - - def __repr__(self) -> str: - """Return repr(self).""" \ No newline at end of file