Station-keeping Sequence

import plotly.express as px
import pandas as pd
import numpy as np

from ostk.core.filesystem import Directory

from ostk.mathematics.curve_fitting import Interpolator
from ostk.mathematics.geometry.d3.object import Cuboid
from ostk.mathematics.geometry.d3.object import Composite
from ostk.mathematics.geometry.d3.object import Point

from ostk.physics import Environment
from ostk.physics.coordinate import Frame
from ostk.physics.environment.atmospheric import Earth as EarthAtmosphericModel
from ostk.physics.environment.gravitational import Earth as EarthGravitationalModel
from ostk.physics.environment.magnetic import Earth as EarthMagneticModel
from ostk.physics.environment.object.celestial import Earth
from ostk.physics.time import DateTime
from ostk.physics.time import Duration
from ostk.physics.time import Instant
from ostk.physics.time import Scale
from ostk.physics.time import Time
from ostk.physics.time import Interval
from ostk.physics.unit import Length
from ostk.physics.unit import Mass

from ostk.astrodynamics import Dynamics
from ostk.astrodynamics.dynamics import Thruster
from ostk.astrodynamics.trajectory.state import NumericalSolver
from ostk.astrodynamics.event_condition import RealCondition
from ostk.astrodynamics.trajectory.orbit.model.brouwerLyddaneMean import (
    BrouwerLyddaneMeanShort,
)
from ostk.astrodynamics.trajectory import StateBuilder
from ostk.astrodynamics.trajectory import Orbit
from ostk.astrodynamics.trajectory import Sequence
from ostk.astrodynamics.trajectory.state import CoordinateSubset
from ostk.astrodynamics.trajectory.state.coordinate_subset import CartesianPosition
from ostk.astrodynamics.trajectory.state.coordinate_subset import CartesianVelocity
from ostk.astrodynamics.trajectory.orbit.model import Tabulated
from ostk.astrodynamics.flight.system import SatelliteSystem
from ostk.astrodynamics.flight.system import PropulsionSystem
from ostk.astrodynamics.guidance_law import ConstantThrust

User inputs

Setup environment

earth = Earth.from_models(
    EarthGravitationalModel(
        EarthGravitationalModel.Type.EGM96, Directory.undefined(), 20, 20
    ),
    EarthMagneticModel(EarthMagneticModel.Type.Undefined),
    EarthAtmosphericModel(EarthAtmosphericModel.Type.Exponential),
)

environment = Environment(Instant.J2000(), [earth])

Initial state

instant = Instant.date_time(DateTime(2023, 1, 1), Scale.UTC)
initial_state = Orbit.sun_synchronous(
    instant, Length.kilometers(505.0), Time.midnight(), Earth.default()
).get_state_at(instant)
dry_mass = Mass.kilograms(100.0)
wet_mass = Mass.kilograms(110.0)

propulsion_system = PropulsionSystem(
    thrust_si_unit=1e-1, specific_impulse_si_unit=3000.0
)

Setup Dynamics, initial state and Satellite System

satellite_geometry = Composite(
    Cuboid(Point(0.0, 0.0, 0.0), np.eye(3).tolist(), [1.0, 0.0, 0.0])
)

satellite_system = SatelliteSystem(
    dry_mass, satellite_geometry, np.eye(3), 500.0, 2.2, propulsion_system
)
state_builder = StateBuilder(
    frame=Frame.GCRF(),
    coordinate_subsets=[
        CartesianPosition.default(),
        CartesianVelocity.default(),
        CoordinateSubset.mass(),
        CoordinateSubset.surface_area(),
        CoordinateSubset.drag_coefficient(),
    ],
)

coordinates = [
    *initial_state.get_coordinates().tolist(),
    wet_mass.in_kilograms(),
    satellite_system.get_cross_sectional_surface_area(),
    satellite_system.get_drag_coefficient(),
]

state = state_builder.build(initial_state.get_instant(), coordinates)
dynamics = Dynamics.from_environment(environment)
numerical_solver = NumericalSolver.default_conditional()

Sequence

sequence = Sequence(
    numerical_solver=numerical_solver,
    dynamics=dynamics,
    maximum_propagation_duration=Duration.days(30.0),
    verbosity=5,
)

evaluator = (
    lambda state: BrouwerLyddaneMeanShort.cartesian(
        (state.get_position(), state.get_velocity()),
        EarthGravitationalModel.EGM2008.gravitational_parameter,
    )
    .get_semi_major_axis()
    .in_meters()
)

# coast till under 495.0
coast_condition = RealCondition(
    name="Mean SMA crossing - coast",
    criterion=RealCondition.Criterion.StrictlyNegative,
    evaluator=evaluator,
    target_value=EarthGravitationalModel.EGM2008.equatorial_radius.in_meters()
    + 495.0e3,
)
sequence.add_coast_segment(coast_condition)

# burn till at 500.0
thrust_condition = RealCondition(
    name="Mean SMA crossing - thrust",
    criterion=RealCondition.Criterion.StrictlyPositive,
    evaluator=evaluator,
    target_value=EarthGravitationalModel.EGM2008.equatorial_radius.in_meters()
    + 500.0e3,
)
sequence.add_maneuver_segment(
    event_condition=thrust_condition,
    thruster_dynamics=Thruster(satellite_system, ConstantThrust.intrack()),
)
sol = sequence.solve(state, 2)
states = sol.get_states()
[2025-07-21 18:40:43.893379] [0x00007f0c221ae740] [debug]   Solving Segment:
-- Segment -----------------------------------------------------------------------------------------
    Name:                                    Coast                                    
    Type:                                    Coast                                    
    Event Condition - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Name:                                    Mean SMA crossing - coast                
    Target:                                  6.87314e+06                              
    Target Type:                             Absolute                                 
    Criterion:                               Strictly Negative                        
    
    Dynamics - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Name:                                    Central Body Gravity [Earth]             
    Name:                                    Atmospheric Drag [Earth]                 
    
    Numerical Solver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Integration logging type:                NoLog                                    
    Integration stepper type:                RungeKuttaDopri5                         
    Integration time step:                   5.0                                      
    Integration relative tolerance:          9.9999999999999998e-13                   
    Integration absolute tolerance:          9.9999999999999998e-13                   
----------------------------------------------------------------------------------------------------
[2025-07-21 18:40:44.616002] [0x00007f0c221ae740] [debug]   
-- Segment Solution --------------------------------------------------------------------------------
    Name:                                    Coast - Mean SMA crossing - coast - 0    
    Condition satisfied:                     True                                     
    Segment type:                            Coast                                    
    Start instant:                           2023-01-01 00:00:00 [UTC]                
    End instant:                             2023-01-01 00:33:37.352.961.924 [UTC]    
    Propagation duration:                    33:37.352.961.924                        
----------------------------------------------------------------------------------------------------


[2025-07-21 18:40:44.616227] [0x00007f0c221ae740] [debug]   Solving Segment:
-- Segment -----------------------------------------------------------------------------------------
    Name:                                    Maneuver                                 
    Type:                                    Maneuver                                 
    Event Condition - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Name:                                    Mean SMA crossing - thrust               
    Target:                                  6.87814e+06                              
    Target Type:                             Absolute                                 
    Criterion:                               Strictly Positive                        
    
    Dynamics - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Name:                                    Central Body Gravity [Earth]             
    Name:                                    Atmospheric Drag [Earth]                 
    Name:                                                                             
    Mass:                                    100.0 [kg]                               
    Geometry - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Objects - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
-- Cuboid ------------------------------------------------------------------------------------------
    Center:                                  [0.0, 0.0, 0.0]                          
    First axis:                              [1.0, 0.0, 0.0]                          
    Second axis:                             [0.0, 1.0, 0.0]                          
    Third axis:                              [0.0, 0.0, 1.0]                          
    First extent:                            1.0                                      
    Second extent:                           0.0                                      
    Third extent:                            0.0                                      
----------------------------------------------------------------------------------------------------
    Inertia Tensor:                          [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] 
    Cross Sectional Surface Area:            500.0                                    
    Drag Coefficient:                        2.2000000000000002                       
    PropulsionSystem - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Thrust:                                  0.10000000000000001                      
    Specific Impulse:                        3000.0                                   
    Mass Flow Rate:                          3.3990540432597613e-06                   
    Name:                                    Constant Thrust                          
    Thrust Direction - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Value:                                   [1.0, 0.0, 0.0]                          
    Local Orbital Frame Factory Type:        VNC                                      
    
    Numerical Solver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Integration logging type:                NoLog                                    
    Integration stepper type:                RungeKuttaDopri5                         
    Integration time step:                   5.0                                      
    Integration relative tolerance:          9.9999999999999998e-13                   
    Integration absolute tolerance:          9.9999999999999998e-13                   
----------------------------------------------------------------------------------------------------
[2025-07-21 18:40:45.851724] [0x00007f0c221ae740] [debug]   
-- Segment Solution --------------------------------------------------------------------------------
    Name:                                    Maneuver - Mean SMA crossing - thrust - 0 
    Condition satisfied:                     True                                     
    Segment type:                            Maneuver                                 
    Start instant:                           2023-01-01 00:33:37.352.961.924 [UTC]    
    End instant:                             2023-01-01 01:36:45.422.767.359 [UTC]    
    Propagation duration:                    01:03:08.069.805.435                     
    Initial mass:                            110.0 [kg]                               
    Final mass:                              109.98712414601253 [kg]                  
    Total mass consumed:                     0.012875853987466712 [kg]                
----------------------------------------------------------------------------------------------------


[2025-07-21 18:40:45.851964] [0x00007f0c221ae740] [debug]   Solving Segment:
-- Segment -----------------------------------------------------------------------------------------
    Name:                                    Coast                                    
    Type:                                    Coast                                    
    Event Condition - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Name:                                    Mean SMA crossing - coast                
    Target:                                  6.87314e+06                              
    Target Type:                             Absolute                                 
    Criterion:                               Strictly Negative                        
    
    Dynamics - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Name:                                    Central Body Gravity [Earth]             
    Name:                                    Atmospheric Drag [Earth]                 
    
    Numerical Solver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Integration logging type:                NoLog                                    
    Integration stepper type:                RungeKuttaDopri5                         
    Integration time step:                   5.0                                      
    Integration relative tolerance:          9.9999999999999998e-13                   
    Integration absolute tolerance:          9.9999999999999998e-13                   
----------------------------------------------------------------------------------------------------
[2025-07-21 18:40:49.353720] [0x00007f0c221ae740] [debug]   
-- Segment Solution --------------------------------------------------------------------------------
    Name:                                    Coast - Mean SMA crossing - coast - 1    
    Condition satisfied:                     True                                     
    Segment type:                            Coast                                    
    Start instant:                           2023-01-01 01:36:45.422.767.359 [UTC]    
    End instant:                             2023-01-01 06:06:57.499.126.416 [UTC]    
    Propagation duration:                    04:30:12.076.359.057                     
----------------------------------------------------------------------------------------------------


[2025-07-21 18:40:49.354390] [0x00007f0c221ae740] [debug]   Solving Segment:
-- Segment -----------------------------------------------------------------------------------------
    Name:                                    Maneuver                                 
    Type:                                    Maneuver                                 
    Event Condition - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Name:                                    Mean SMA crossing - thrust               
    Target:                                  6.87814e+06                              
    Target Type:                             Absolute                                 
    Criterion:                               Strictly Positive                        
    
    Dynamics - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Name:                                    Central Body Gravity [Earth]             
    Name:                                    Atmospheric Drag [Earth]                 
    Name:                                                                             
    Mass:                                    100.0 [kg]                               
    Geometry - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Objects - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
-- Cuboid ------------------------------------------------------------------------------------------
    Center:                                  [0.0, 0.0, 0.0]                          
    First axis:                              [1.0, 0.0, 0.0]                          
    Second axis:                             [0.0, 1.0, 0.0]                          
    Third axis:                              [0.0, 0.0, 1.0]                          
    First extent:                            1.0                                      
    Second extent:                           0.0                                      
    Third extent:                            0.0                                      
----------------------------------------------------------------------------------------------------
    Inertia Tensor:                          [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] 
    Cross Sectional Surface Area:            500.0                                    
    Drag Coefficient:                        2.2000000000000002                       
    PropulsionSystem - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Thrust:                                  0.10000000000000001                      
    Specific Impulse:                        3000.0                                   
    Mass Flow Rate:                          3.3990540432597613e-06                   
    Name:                                    Constant Thrust                          
    Thrust Direction - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Value:                                   [1.0, 0.0, 0.0]                          
    Local Orbital Frame Factory Type:        VNC                                      
    
    Numerical Solver - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    Integration logging type:                NoLog                                    
    Integration stepper type:                RungeKuttaDopri5                         
    Integration time step:                   5.0                                      
    Integration relative tolerance:          9.9999999999999998e-13                   
    Integration absolute tolerance:          9.9999999999999998e-13                   
----------------------------------------------------------------------------------------------------
[2025-07-21 18:40:50.591997] [0x00007f0c221ae740] [debug]   
-- Segment Solution --------------------------------------------------------------------------------
    Name:                                    Maneuver - Mean SMA crossing - thrust - 1 
    Condition satisfied:                     True                                     
    Segment type:                            Maneuver                                 
    Start instant:                           2023-01-01 06:06:57.499.126.416 [UTC]    
    End instant:                             2023-01-01 07:09:30.418.381.867 [UTC]    
    Propagation duration:                    01:02:32.919.255.451                     
    Initial mass:                            109.98712414601253 [kg]                  
    Final mass:                              109.97436777064368 [kg]                  
    Total mass consumed:                     0.012756375368851991 [kg]                
------------------------------------------------------
----------------------------------------------
model = Tabulated(states, 0, Interpolator.Type.BarycentricRational)
orbit = Orbit(model, environment.access_celestial_object_with_name("Earth"))
interval = Interval.closed(states[0].get_instant(), states[-1].get_instant())
interpolated_states = orbit.get_states_at(
    interval.generate_grid(Duration.seconds(20.0))
)
data = []
for state in interpolated_states:
    blmshort = BrouwerLyddaneMeanShort.cartesian(
        (state.get_position(), state.get_velocity()),
        earth.get_gravitational_parameter(),
    )
    data.append(
        {
            "altitude": float(
                blmshort.get_semi_major_axis().in_kilometers()
                - earth.get_equatorial_radius().in_kilometers()
            ),
            "mass": float(state.get_coordinates()[6]),
            "time": state.get_instant().get_date_time(Scale.UTC),
        }
    )
df = pd.DataFrame(data)
figure = px.scatter(
    df,
    x="time",
    y="altitude",
    color="mass",
    height=600,
    width=1200,
)
for segment_solution in sol.segment_solutions:
    time = segment_solution.states[-1].get_instant().get_date_time(Scale.UTC)
    figure.add_vline(x=time.isoformat())
figure.show("png")
../_images/4a4dd0355d7f9879e97dfb87002dca9585f3119cc9c5a0e9d2ba81e5582dc98a.png
figure2 = px.line(
    df,
    x="time",
    y="mass",
    height=600,
    width=1200,
)
for segment_solution in sol.segment_solutions:
    time = segment_solution.states[-1].get_instant().get_date_time(Scale.UTC)
    figure2.add_vline(x=time.isoformat())
figure2.show("png")
../_images/a6fb89c8d55ca7d7503c358bd31a71b6757eea42736b4a8be518df992949bd3f.png