From 67b15ec03459e32d875a34b118bd1d4d26b47d52 Mon Sep 17 00:00:00 2001 From: Christopher Rabotin Date: Fri, 1 Nov 2024 21:35:44 -0600 Subject: [PATCH] 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)