Skip to content

Commit 668f536

Browse files
phc1990Pau Hebrero
andauthored
fix: filter out leading zero mass flow rates in Maneuvers (#646)
* test: add regression test * wip: for Vishwa * Revert "wip: for Vishwa" This reverts commit 1af201d. * fix: filter out leading zeroes on maneuver profiles * refactor: apply PR suggestions --------- Co-authored-by: Pau Hebrero <pau.hebrero@gmail.com>
1 parent 2654e6d commit 668f536

File tree

4 files changed

+406
-138
lines changed

4 files changed

+406
-138
lines changed

src/OpenSpaceToolkit/Astrodynamics/Flight/Maneuver.cpp

Lines changed: 94 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -42,50 +42,124 @@ const Array<Shared<const CoordinateSubset>> Maneuver::RequiredCoordinateSubsets
4242
const Shared<const CoordinateSubset> Maneuver::DefaultAccelerationCoordinateSubsetSPtr = RequiredCoordinateSubsets[2];
4343

4444
Maneuver::Maneuver(const Array<State>& aStateArray)
45-
: states_(aStateArray)
4645
{
47-
// Sanitize the inputs
48-
if (this->states_.isEmpty())
46+
if (aStateArray.isEmpty())
4947
{
5048
throw ostk::core::error::RuntimeError("No states provided.");
5149
}
5250

53-
for (const auto& coordinateSubset : RequiredCoordinateSubsets)
51+
// Check if the states are sorted and contain all required coordinate subsets
52+
for (Size k = 0; k < aStateArray.getSize(); ++k)
5453
{
55-
if (!std::all_of(
56-
states_.begin(),
57-
states_.end(),
58-
[&coordinateSubset](const State& aState)
59-
{
60-
return aState.hasSubset(coordinateSubset);
61-
}
62-
))
54+
const State& state = aStateArray[k];
55+
56+
if (k < aStateArray.getSize() - 1)
57+
{
58+
const State& nextState = aStateArray[k + 1];
59+
if (state.accessInstant() >= nextState.accessInstant())
60+
{
61+
throw ostk::core::error::runtime::Wrong(
62+
"Unsorted or Duplicate State Array",
63+
String::Format(
64+
"Index {}: {} > Index {}: {}",
65+
k,
66+
state.accessInstant().toString(),
67+
k + 1,
68+
nextState.accessInstant().toString()
69+
)
70+
);
71+
}
72+
}
73+
74+
for (const auto& coordinateSubset : RequiredCoordinateSubsets)
6375
{
64-
throw ostk::core::error::RuntimeError(String::Format("{} not found in states.", coordinateSubset->getName())
65-
);
76+
if (!state.hasSubset(coordinateSubset))
77+
{
78+
throw ostk::core::error::RuntimeError(String::Format(
79+
"Coordinate Subset {} not found in states at index {}.", coordinateSubset->getName(), k
80+
));
81+
}
6682
}
6783
}
6884

69-
const Duration maneuverDuration = states_.accessLast().accessInstant() - states_.accessFirst().accessInstant();
85+
// Sanitize the states:
86+
// - Check there are no positive mass flow rate states
87+
// - Remove all leading zero mass flow rate states
88+
// - Allow a single trailing zero mass flow rate state
89+
Array<State> sanitizedStates = Array<State>::Empty();
7090
Duration largestInterval = Duration::Zero();
91+
bool maneuverStartFound = false;
92+
bool maneuverEndFound = false;
7193

72-
for (Size k = 0; k < states_.getSize() - 1; ++k)
94+
for (Size k = 0; k < aStateArray.getSize(); k++)
7395
{
74-
if (states_[k].accessInstant() >= states_[k + 1].accessInstant())
96+
const State& state = aStateArray[k];
97+
const Real massFlowRate = state.extractCoordinate(RequiredCoordinateSubsets[3])[0];
98+
99+
// Check mass flow rate is not positive
100+
if (massFlowRate > 0.0)
101+
{
102+
throw ostk::core::error::RuntimeError(String::Format("Positive mass flow rate at index {}.", k));
103+
}
104+
105+
if (massFlowRate < 0.0)
106+
{
107+
// check that once we register a zero, no more negative mass flow rate states are found afterwards
108+
if (maneuverEndFound)
109+
{
110+
throw ostk::core::error::RuntimeError(
111+
String::Format("Negative mass flow rate at index {} after a zero mass flow rate.", k)
112+
);
113+
}
114+
115+
maneuverStartFound = true;
116+
if (!sanitizedStates.isEmpty())
117+
{
118+
largestInterval =
119+
std::max(largestInterval, state.accessInstant() - sanitizedStates.accessLast().accessInstant());
120+
}
121+
sanitizedStates.add(state);
122+
continue;
123+
}
124+
125+
// (From here onwards it's only zero mass flow rate states)
126+
127+
// Don't add leading zero mass flow rate states
128+
if (!maneuverStartFound)
129+
{
130+
continue;
131+
}
132+
133+
// Add only one trailing zero mass flow rate state
134+
if (!maneuverEndFound)
75135
{
76-
throw ostk::core::error::runtime::Wrong("Unsorted or Duplicate State Array");
136+
maneuverEndFound = true;
137+
if (!sanitizedStates.isEmpty())
138+
{
139+
largestInterval =
140+
std::max(largestInterval, state.accessInstant() - sanitizedStates.accessLast().accessInstant());
141+
}
142+
sanitizedStates.add(state);
143+
continue;
77144
}
145+
}
78146

79-
largestInterval = std::max(largestInterval, states_[k + 1].accessInstant() - states_[k].accessInstant());
147+
if (sanitizedStates.isEmpty())
148+
{
149+
throw ostk::core::error::RuntimeError("No states left after sanitization.");
80150
}
81151

152+
// Check the largest interval between states is within the recommended limits
82153
if (largestInterval > Maneuver::MaximumRecommendedInterpolationInterval)
83154
{
84155
std::cout << "WARNING: Some intervals between the instants defined for this Maneuver are larger than the "
85156
"maximum recommended interpolation interval of "
86157
<< Maneuver::MaximumRecommendedInterpolationInterval.inSeconds() << " seconds." << std::endl;
87158
}
88159

160+
// Check the maneuver duration is within the recommended limits
161+
const Duration maneuverDuration =
162+
sanitizedStates.accessLast().accessInstant() - sanitizedStates.accessFirst().accessInstant();
89163
if (maneuverDuration < Maneuver::MinimumRecommendedDuration)
90164
{
91165
std::cout
@@ -95,25 +169,7 @@ Maneuver::Maneuver(const Array<State>& aStateArray)
95169
<< std::endl;
96170
}
97171

98-
// Ensure that mass flow rate profile is expressed in strictly negative numbers
99-
if (std::any_of(
100-
states_.begin(),
101-
states_.end() - 1,
102-
[](const State& aState)
103-
{
104-
return aState.extractCoordinate(RequiredCoordinateSubsets[3])[0] >= 0.0;
105-
}
106-
))
107-
{
108-
throw ostk::core::error::RuntimeError(
109-
"Mass flow rate profile must have strictly negative values (except the last state which may be zero)."
110-
);
111-
}
112-
113-
if (states_.accessLast().extractCoordinate(RequiredCoordinateSubsets[3])[0] > 0.0)
114-
{
115-
throw ostk::core::error::RuntimeError("Last state must have non-positive mass flow rate.");
116-
}
172+
states_ = sanitizedStates;
117173
}
118174

119175
bool Maneuver::operator==(const Maneuver& aManeuver) const

src/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.cpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,22 +1333,18 @@ Segment::Solution Segment::solveManeuverForInterval_(
13331333
const State& aState, const Shared<Thruster>& thrusterDynamics, const Interval& validManeuverInterval
13341334
) const
13351335
{
1336-
Array<State> states = Array<State>::Empty();
1337-
13381336
// Coast until the start of the maneuver to ensure we begin solving the maneuver at the exact start instant
1339-
const Array<State> coastStates =
1340-
propagateWithDynamics_(aState, validManeuverInterval.getStart(), freeDynamicsArray_);
1341-
states.add(std::move(coastStates));
1337+
Array<State> states = propagateWithDynamics_(aState, validManeuverInterval.getStart(), freeDynamicsArray_);
13421338

13431339
const Array<Shared<Dynamics>> dynamicsArray = freeDynamicsArray_ + Array<Shared<Dynamics>> {thrusterDynamics};
13441340

13451341
// Solve the maneuver for just the defined interval
1346-
const State lastState = coastStates.isEmpty() ? aState : coastStates.accessLast();
1342+
const State lastState = states.isEmpty() ? aState : states.accessLast();
13471343
const Array<State> maneuverStates =
13481344
propagateWithDynamics_(lastState, validManeuverInterval.getEnd(), dynamicsArray);
13491345

13501346
// Skip the first maneuver state if we have coast states (it duplicates the last coast state)
1351-
const auto maneuverStartIter = coastStates.isEmpty() ? maneuverStates.begin() : maneuverStates.begin() + 1;
1347+
const auto maneuverStartIter = states.isEmpty() ? maneuverStates.begin() : maneuverStates.begin() + 1;
13521348
states.add(Array<State>(maneuverStartIter, maneuverStates.end()));
13531349

13541350
return {

0 commit comments

Comments
 (0)