Skip to content

Commit f32a854

Browse files
authored
feat!: add minimum maneuver duration constraint (#541)
* feat: add minimum maneuver duration constraint * fix: print test * feat: address feedback
1 parent d718fd9 commit f32a854

File tree

7 files changed

+368
-39
lines changed

7 files changed

+368
-39
lines changed

bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Sequence.cpp

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -209,16 +209,18 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Trajectory_Sequence(pybind11::module
209209
const NumericalSolver&,
210210
const Array<Shared<Dynamics>>&,
211211
const Duration&,
212+
const Duration&,
212213
const Size&>(),
213214
R"doc(
214215
Construct a new `Sequence` object.
215216
216217
Args:
217-
segments (list[Segment], optional): The segments.
218-
numerical_solver (NumericalSolver, optional): The numerical solver.
219-
dynamics (list[Dynamics], optional): The dynamics.
220-
maximum_propagation_duration (Duration, optional): The maximum propagation duration.
221-
verbosity (int, optional): The verbosity level.
218+
segments (list[Segment], optional): The segments. Defaults to an empty list.
219+
numerical_solver (NumericalSolver, optional): The numerical solver. Defaults to the default conditional numerical solver.
220+
dynamics (list[Dynamics], optional): The dynamics. Defaults to an empty list.
221+
maximum_propagation_duration (Duration, optional): The maximum propagation duration. Defaults to 30 days.
222+
minimum_maneuver_duration (Duration, optional): The minimum maneuver duration. Defaults to Undefined. If defined, maneuvers less than this duration will be skipped.
223+
verbosity (int, optional): The verbosity level. Defaults to 1.
222224
223225
Returns:
224226
Sequence: The new `Sequence` object.
@@ -229,7 +231,8 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Trajectory_Sequence(pybind11::module
229231
"numerical_solver", NumericalSolver::DefaultConditional(), "NumericalSolver.default_conditional()"
230232
),
231233
arg_v("dynamics", Array<Shared<Dynamics>>::Empty(), "[]"),
232-
arg_v("maximum_propagation_duration", Duration::Days(30.0), "duration.days(30.0)"),
234+
arg_v("maximum_propagation_duration", Duration::Days(30.0), "Duration.days(30.0)"),
235+
arg_v("minimum_maneuver_duration", Duration::Undefined(), "Duration.undefined()"),
233236
arg("verbosity") = 1
234237
)
235238

@@ -280,6 +283,17 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Trajectory_Sequence(pybind11::module
280283
281284
)doc"
282285
)
286+
.def(
287+
"get_minimum_maneuver_duration",
288+
&Sequence::getMinimumManeuverDuration,
289+
R"doc(
290+
Get the minimum maneuver duration.
291+
292+
Returns:
293+
Duration: The minimum maneuver duration.
294+
295+
)doc"
296+
)
283297

284298
.def(
285299
"add_segment",

bindings/python/test/trajectory/test_sequence.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -156,17 +156,7 @@ def state(
156156

157157
return State(
158158
instant,
159-
[
160-
717094.039086306,
161-
-6872433.2241124,
162-
46175.9696673281,
163-
-970.650826004612,
164-
-45.4598114773158,
165-
7529.82424886455,
166-
dry_mass.in_kilograms() + wet_mass.in_kilograms(),
167-
cross_sectional_surface_area,
168-
drag_coefficient,
169-
],
159+
coordinates,
170160
frame,
171161
coordinate_broker,
172162
)
@@ -351,6 +341,28 @@ def sequence_solution(
351341
)
352342

353343

344+
@pytest.fixture
345+
def minimum_maneuver_duration():
346+
return Duration.minutes(1.0)
347+
348+
349+
@pytest.fixture
350+
def sequence_with_minimum_maneuver_duration(
351+
segments: list[Segment],
352+
numerical_solver: NumericalSolver,
353+
dynamics: list,
354+
maximum_propagation_duration: Duration,
355+
minimum_maneuver_duration: Duration,
356+
):
357+
return Sequence(
358+
segments=segments,
359+
dynamics=dynamics,
360+
numerical_solver=numerical_solver,
361+
maximum_propagation_duration=maximum_propagation_duration,
362+
minimum_maneuver_duration=minimum_maneuver_duration,
363+
)
364+
365+
354366
class TestSequenceSolution:
355367
def test_properties(
356368
self,
@@ -421,6 +433,16 @@ def test_get_maximum_propagation_duration(
421433
):
422434
assert sequence.get_maximum_propagation_duration() == maximum_propagation_duration
423435

436+
def test_get_minimum_maneuver_duration(
437+
self,
438+
sequence_with_minimum_maneuver_duration: Sequence,
439+
minimum_maneuver_duration: Duration,
440+
):
441+
assert (
442+
sequence_with_minimum_maneuver_duration.get_minimum_maneuver_duration()
443+
== minimum_maneuver_duration
444+
)
445+
424446
def test_add_segment(
425447
self,
426448
sequence: Sequence,

include/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,14 @@ class Segment
6868
///
6969
/// @param aName Name of the segment
7070
/// @param aDynamicsArray Array of dynamics
71-
/// @param aStates Array of states for the segment
71+
/// @param aStateArray Array of states for the segment
7272
/// @param aConditionIsSatisfied True if the event condition is satisfied
7373
/// @param aSegmentType Type of segment
7474
/// @return An instance of Solution
7575
Solution(
7676
const String& aName,
7777
const Array<Shared<Dynamics>>& aDynamicsArray,
78-
const Array<State>& aStates,
78+
const Array<State>& aStateArray,
7979
const bool& aConditionIsSatisfied,
8080
const Segment::Type& aSegmentType
8181
);

include/OpenSpaceToolkit/Astrodynamics/Trajectory/Sequence.hpp

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,25 +125,29 @@ class Sequence
125125
/// const Array<Shared<Dynamics>> dynamicsArray =
126126
/// {std::make_shared<CentralBodyGravity>(Earth::GravitationalParameter())};
127127
/// const Duration maximumPropagationDuration = Duration::Days(7.0);
128+
/// const Duration minimumManeuverDuration = Duration::Zero();
128129
/// const Size verbosity = 0;
129130
///
130131
/// Sequence sequence = {segmentArray, numericalSolver, dynamicsArray,
131-
/// maximumPropagationDuration, verbosity};
132+
/// maximumPropagationDuration, minimumManeuverDuration, verbosity};
132133
///
133134
/// @endcode
134135
///
135136
/// @param aSegmentArray An array of segments. Defaults to empty.
136-
/// @param aNumericalSolver A Numerical Solver. Defaults to Undefined.
137+
/// @param aNumericalSolver A Numerical Solver. Defaults to NumericalSolver::DefaultConditional().
137138
/// @param aDynamicsArray An array of shared dynamics. Defaults to empty.
138-
/// @param segmentPropagationDurationLimit Maximum duration for propagation. Defaults to 7.0
139+
/// @param aSegmentPropagationDurationLimit Maximum duration for propagation. Defaults to 30.0
139140
/// days.
140-
/// @param verbosity Verbosity level for the solver [0 (low) - 5 (high)]. Defaults to 0.
141+
/// @param aMinimumManeuverDuration Minimum duration for maneuver, maneuvers less than this duration
142+
/// will be skipped. Defaults to Undefined.
143+
/// @param aVerbosityLevel Verbosity level for the solver [0 (low) - 5 (high)]. Defaults to 0.
141144
Sequence(
142145
const Array<Segment>& aSegmentArray = Array<Segment>::Empty(),
143-
const NumericalSolver& aNumericalSolver = NumericalSolver::Undefined(),
146+
const NumericalSolver& aNumericalSolver = NumericalSolver::DefaultConditional(),
144147
const Array<Shared<Dynamics>>& aDynamicsArray = Array<Shared<Dynamics>>::Empty(),
145-
const Duration& segmentPropagationDurationLimit = Duration::Days(7.0),
146-
const Size& verbosity = 0
148+
const Duration& aSegmentPropagationDurationLimit = Duration::Days(30.0),
149+
const Duration& aMinimumManeuverDuration = Duration::Undefined(),
150+
const Size& aVerbosityLevel = 0
147151
);
148152

149153
/// @brief Output stream operator.
@@ -173,6 +177,11 @@ class Sequence
173177
/// @return Maximum propagation duration.
174178
Duration getMaximumPropagationDuration() const;
175179

180+
/// @brief Get minimum maneuver duration.
181+
///
182+
/// @return Minimum maneuver duration.
183+
Duration getMinimumManeuverDuration() const;
184+
176185
/// @brief Add a trajectory segment.
177186
///
178187
/// @param aTrajectorySegment A trajectory segment.
@@ -224,6 +233,7 @@ class Sequence
224233
NumericalSolver numericalSolver_;
225234
Array<Shared<Dynamics>> dynamics_;
226235
Duration segmentPropagationDurationLimit_;
236+
Duration minimumManeuverDuration_;
227237
};
228238

229239
} // namespace trajectory

src/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.cpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ using ostk::astrodynamics::trajectory::StateBuilder;
3535
Segment::Solution::Solution(
3636
const String& aName,
3737
const Array<Shared<Dynamics>>& aDynamicsArray,
38-
const Array<State>& aStates,
38+
const Array<State>& aStateArray,
3939
const bool& aConditionIsSatisfied,
4040
const Segment::Type& aSegmentType
4141
)
4242
: name(aName),
4343
dynamics(aDynamicsArray),
44-
states(aStates),
44+
states(aStateArray),
4545
conditionIsSatisfied(aConditionIsSatisfied),
4646
segmentType(aSegmentType)
4747
{
@@ -505,10 +505,21 @@ Segment::Solution Segment::solve(const State& aState, const Duration& maximumPro
505505
aState, aState.accessInstant() + maximumPropagationDuration, *eventCondition_
506506
);
507507

508+
// Expand states based on input state
509+
const StateBuilder stateBuilder = {aState};
510+
511+
Array<State> states = Array<State>::Empty();
512+
states.reserve(propagator.accessNumericalSolver().accessObservedStates().getSize());
513+
514+
for (const State& state : propagator.accessNumericalSolver().accessObservedStates())
515+
{
516+
states.add(stateBuilder.expand(state.inFrame(aState.accessFrame()), aState));
517+
}
518+
508519
return {
509520
name_,
510521
dynamics_,
511-
propagator.accessNumericalSolver().accessObservedStates(),
522+
states,
512523
conditionSolution.conditionIsSatisfied,
513524
type_,
514525
};

src/OpenSpaceToolkit/Astrodynamics/Trajectory/Sequence.cpp

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -183,35 +183,47 @@ Sequence::Sequence(
183183
const Array<Segment>& aSegmentArray,
184184
const NumericalSolver& aNumericalSolver,
185185
const Array<Shared<Dynamics>>& aDynamicsArray,
186-
const Duration& maximumPropagationDuration,
187-
const Size& verbosity
186+
const Duration& aMaximumPropagationDuration,
187+
const Duration& aMinimumManeuverDuration,
188+
const Size& aVerbosityLevel
188189
)
189190
: segments_(aSegmentArray),
190191
numericalSolver_(aNumericalSolver),
191192
dynamics_(aDynamicsArray),
192-
segmentPropagationDurationLimit_(maximumPropagationDuration)
193+
segmentPropagationDurationLimit_(aMaximumPropagationDuration),
194+
minimumManeuverDuration_(aMinimumManeuverDuration)
193195
{
194-
if (verbosity == 5)
196+
if (aMaximumPropagationDuration <= Duration::Zero())
197+
{
198+
throw ostk::core::error::RuntimeError("Maximum propagation duration must be strictly positive.");
199+
}
200+
201+
if (aMinimumManeuverDuration.isDefined() && aMinimumManeuverDuration <= Duration::Zero())
202+
{
203+
throw ostk::core::error::RuntimeError("Minimum maneuver duration must be strictly positive.");
204+
}
205+
206+
if (aVerbosityLevel == 5)
195207
{
196208
boost::log::core::get()->set_filter(boost::log::trivial::severity >= boost::log::trivial::trace);
197209
}
198-
else if (verbosity == 4)
210+
else if (aVerbosityLevel == 4)
199211
{
200212
boost::log::core::get()->set_filter(boost::log::trivial::severity >= boost::log::trivial::debug);
201213
}
202-
else if (verbosity == 3)
214+
else if (aVerbosityLevel == 3)
203215
{
204216
boost::log::core::get()->set_filter(boost::log::trivial::severity >= boost::log::trivial::info);
205217
}
206-
else if (verbosity == 2)
218+
else if (aVerbosityLevel == 2)
207219
{
208220
boost::log::core::get()->set_filter(boost::log::trivial::severity >= boost::log::trivial::warning);
209221
}
210-
else if (verbosity == 1)
222+
else if (aVerbosityLevel == 1)
211223
{
212224
boost::log::core::get()->set_filter(boost::log::trivial::severity >= boost::log::trivial::error);
213225
}
214-
else if (verbosity == 0)
226+
else if (aVerbosityLevel == 0)
215227
{
216228
boost::log::core::get()->set_filter(boost::log::trivial::severity >= boost::log::trivial::fatal);
217229
}
@@ -248,6 +260,11 @@ Duration Sequence::getMaximumPropagationDuration() const
248260
return segmentPropagationDurationLimit_;
249261
}
250262

263+
Duration Sequence::getMinimumManeuverDuration() const
264+
{
265+
return minimumManeuverDuration_;
266+
}
267+
251268
void Sequence::addSegment(const Segment& aSegment)
252269
{
253270
segments_.add(aSegment);
@@ -295,6 +312,18 @@ Sequence::Solution Sequence::solve(const State& aState, const Size& aRepetitionC
295312

296313
BOOST_LOG_TRIVIAL(debug) << "\n" << segmentSolution << std::endl;
297314

315+
if (segment.getType() == Segment::Type::Maneuver && minimumManeuverDuration_.isDefined())
316+
{
317+
if (segmentSolution.getPropagationDuration() < minimumManeuverDuration_)
318+
{
319+
BOOST_LOG_TRIVIAL(debug)
320+
<< "Maneuver duration is less than the minimum maneuver duration. Skipping this maneuver."
321+
<< std::endl;
322+
323+
continue;
324+
}
325+
}
326+
298327
segmentSolutions.add(segmentSolution);
299328

300329
// Terminate Sequence unsuccessfully if the segment condition was not satisfied
@@ -346,6 +375,19 @@ Sequence::Solution Sequence::solveToCondition(
346375

347376
BOOST_LOG_TRIVIAL(debug) << "\n" << segmentSolution << std::endl;
348377

378+
// Skip maneuver if it is less than the minimum maneuver duration
379+
if (segment.getType() == Segment::Type::Maneuver && minimumManeuverDuration_.isDefined())
380+
{
381+
if (segmentSolution.getPropagationDuration() < minimumManeuverDuration_)
382+
{
383+
BOOST_LOG_TRIVIAL(debug)
384+
<< "Maneuver duration is less than the minimum maneuver duration. Skipping this maneuver."
385+
<< std::endl;
386+
387+
continue;
388+
}
389+
}
390+
349391
segmentSolutions.add(segmentSolution);
350392

351393
// Terminate Sequence unsuccessfully if the segment condition was not satisfied
@@ -405,6 +447,10 @@ void Sequence::print(std::ostream& anOutputStream, bool displayDecorator) const
405447
ostk::core::utils::Print::Line(anOutputStream)
406448
<< "Maximum Propagation Duration:" << segmentPropagationDurationLimit_.toString();
407449

450+
ostk::core::utils::Print::Line(anOutputStream)
451+
<< "Minimum Maneuver Duration:"
452+
<< (minimumManeuverDuration_.isDefined() ? minimumManeuverDuration_.toString() : "Undefined");
453+
408454
if (displayDecorator)
409455
{
410456
ostk::core::utils::Print::Footer(anOutputStream);

0 commit comments

Comments
 (0)