Skip to content

Commit

Permalink
feat: Python class meta-programming for State construction (#244)
Browse files Browse the repository at this point in the history
* feat: add StateTemplateType emitter in Python to create custom State types

* test: add copy constructor tests

* feat: add non-default copy-assignment operator

* style: formatting

* Update bindings/python/test/trajectory/test_state.py

* Apply suggestions from code review

Co-authored-by: Vishwa Shah <vishwa2710@gmail.com>

* test: fix old naming in python test and address 1 MR comment

---------

Co-authored-by: Vishwa Shah <vishwa2710@gmail.com>
  • Loading branch information
kyle-cochran and vishwa2710 authored Oct 13, 2023
1 parent e93ad85 commit 2200a55
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Trajectory_State(pybind11::module& a
arg("frame"),
arg("coordinates_subsets")
)
.def(init<const State&>(), arg("state"))

.def(self == self)
.def(self != self)
Expand Down
32 changes: 32 additions & 0 deletions bindings/python/test/trajectory/test_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,38 @@ def test_subsets_constructor(
assert isinstance(state, State)
assert state.is_defined()

def test_custom_state_class_generator(
self,
instant: Instant,
position: Position,
velocity: Velocity,
frame: Frame,
):
state = State(
instant,
np.append(position.get_coordinates(), velocity.get_coordinates()),
frame,
[CartesianPosition.default(), CartesianVelocity.default()],
)

MySuperFunState: type = State.template(
frame,
[CartesianPosition.default(), CartesianVelocity.default()],
)

custom_state: MySuperFunState = MySuperFunState(
instant,
np.append(position.get_coordinates(), velocity.get_coordinates()),
)

assert custom_state is not None
assert isinstance(custom_state, MySuperFunState)
assert isinstance(state, State)
assert custom_state.is_defined()

assert custom_state == state
assert custom_state is not state

def test_comparators(self, state: State):
assert (state == state) is True
assert (state != state) is False
Expand Down
6 changes: 6 additions & 0 deletions bindings/python/tools/python/ostk/astrodynamics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@
from ostk.physics import *

from .OpenSpaceToolkitAstrodynamicsPy import *

from .pytrajectory.pystate import State as PyState

trajectory.State = (
PyState # Override the pure c++ State class with the modified Python one
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Apache License 2.0
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Apache License 2.0


# Python-only State functionality
import numpy as np

from ostk.physics.coordinate import Frame
from ostk.physics.time import Instant

from ostk.astrodynamics.trajectory import State, StateBuilder


@staticmethod
def custom_class_generator(frame: Frame, coordinates_subsets: list) -> type:
"""
Emit a custom class type for States. This is meta-programming syntactic sugar on top of the StateBuilder class.
StateType = State.template(frame, coordinates_subsets)
state = StateType(instant, coordinates)
is equivalent to
state_builder = StateBuilder(frame, coordinates_subsets)
state = state_builder.build(instant, coordinates)
"""

class StateTemplateType(State):
state_builder: StateBuilder = StateBuilder(frame, coordinates_subsets)

def __init__(self, instant: Instant, coordinates: np.ndarray):
super().__init__(
StateTemplateType.state_builder.build(instant, coordinates)
)

return StateTemplateType


State.template = custom_class_generator
13 changes: 13 additions & 0 deletions include/OpenSpaceToolkit/Astrodynamics/Trajectory/State.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,19 @@ class State

State(const Instant& anInstant, const Position& aPosition, const Velocity& aVelocity);

/// @brief Copy constructor.
///
/// @param [in] aState An existing State

State(const State& aState);

/// @brief Copy-assignment operator
///
/// @param [in] aState The State to copy
/// @return The modified State

State& operator=(const State& aState);

/// @brief Equality operator.
///
/// @param [in] aState The State to compare to
Expand Down
20 changes: 20 additions & 0 deletions src/OpenSpaceToolkit/Astrodynamics/Trajectory/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ State::State(const Instant& anInstant, const Position& aPosition, const Velocity
this->coordinatesBrokerSPtr_ = coordinatesBrokerSPtr;
}

State::State(const State& aState)
: instant_(aState.instant_),
coordinates_(aState.coordinates_),
frameSPtr_(aState.frameSPtr_),
coordinatesBrokerSPtr_(aState.coordinatesBrokerSPtr_)
{
}

State& State::operator=(const State& aState)
{
if (this != &aState)
{
instant_ = aState.instant_;
coordinates_ = aState.coordinates_;
frameSPtr_ = aState.frameSPtr_;
coordinatesBrokerSPtr_ = aState.coordinatesBrokerSPtr_;
}
return *this;
}

bool State::operator==(const State& aState) const
{
if ((!this->isDefined()) || (!aState.isDefined()))
Expand Down
46 changes: 46 additions & 0 deletions test/OpenSpaceToolkit/Astrodynamics/Trajectory/State.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,52 @@ TEST(OpenSpaceToolkit_Astrodynamics_Trajectory_State, Constructor)

EXPECT_ANY_THROW(State state(instant, position, velocity););
}

{
const Instant instant = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 0), Scale::UTC);
VectorXd coordinates(6);
coordinates << 1.0, 2.0, 3.0, 4.0, 5.0, 6.0;
const Shared<const CoordinatesBroker> brokerSPtr = std::make_shared<CoordinatesBroker>(
CoordinatesBroker({CartesianPosition::Default(), CartesianVelocity::Default()})
);

State state(instant, coordinates, Frame::GCRF(), brokerSPtr);

EXPECT_NO_THROW(State anotherState(state));

const State anotherState(state);

EXPECT_EQ(state, anotherState);
}
}

TEST(OpenSpaceToolkit_Astrodynamics_Trajectory_State, CopyAssignmentOperator)
{
// Create a state
const Instant instant = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 0), Scale::UTC);
VectorXd coordinates(6);
coordinates << 1.0, 2.0, 3.0, 4.0, 5.0, 6.0;
const Shared<const CoordinatesBroker> brokerSPtr = std::make_shared<CoordinatesBroker>(
CoordinatesBroker({CartesianPosition::Default(), CartesianVelocity::Default()})
);
State aState = {instant, coordinates, Frame::GCRF(), brokerSPtr};

// Copy the state
State anotherState = State::Undefined();

// Check that the two states are no longer equal
EXPECT_NE(aState, anotherState);

anotherState = aState;

// Check that the two states are equal
EXPECT_EQ(aState, anotherState);

// Modify the original state
aState = State::Undefined();

// Check that the two states are no longer equal
EXPECT_NE(aState, anotherState);
}

TEST(OpenSpaceToolkit_Astrodynamics_Trajectory_State, EqualToOperator)
Expand Down

0 comments on commit 2200a55

Please sign in to comment.