From 7d0587e8c41aee2144425cf60b4521e5fa2266a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20G=C3=B3mez?= Date: Sat, 22 Aug 2026 12:47:13 +0200 Subject: [PATCH 1/3] docs: fix README examples that do not run as printed Roughly a third of the runnable snippets failed when executed verbatim against an installed 0.2.1. Fixes, in order of appearance: - known-actors snippet referenced an undefined 'earth' - TLE and radiation snippets used 'pk' without importing pykep - 'accessing the orbit' used pk.epoch(), which pykep 2.x has no constructor for; epoch_from_string is what the README itself uses two sections later - power and thermal snippets attached models to actors with no orbit, so both raised on the very next line - model_data_corruption was documented with exposure_time_in_s; the parameter is exposure_period_in_s - load_default_cfg was called without being imported in four snippets - the 'using the cfg' snippet built a cfg and then called init_sim without it, defeating the point of the section - the constraint-function snippet read temperature_in_K without ever setting a thermal model, though the prose says one is required - simulation_time was described as 'seconds since the start'; with the default cfg it is seconds since MJD2000 - the visualization notebook is under examples/, not paseos/ - the logging_interval example set a value below cfg.sim.dt, where it cannot take effect - the two-body vs SGP4 deviation figures were overstated at short horizons; measured drift is tens of km in the first hours Also drops the stale 'singleton' claim from init_sim's docstring, which contradicted the README's documented multi-instance support. --- README.md | 68 ++++++++++++++++++++++++++++++++++++++-------- paseos/__init__.py | 3 +- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index e0f2c68..c3773f8 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,8 @@ other_spacraft_actor = ActorBuilder.get_actor_scaffold(name="other_sat", ActorBuilder.set_orbit(actor=other_spacraft_actor, position=[-10000000, 0, 0], velocity=[0, -8000.0, 0], - epoch=pk.epoch(0), central_body=earth) + epoch=pk.epoch(0), + central_body=pk.planet.jpl_lp("earth")) #Create GroundstationActor grndStation = GroundstationActor(name="grndStation", epoch=pk.epoch(0)) @@ -277,13 +278,14 @@ ActorBuilder.set_orbit(actor=sat_actor, epoch=pk.epoch(0), central_body=earth) ``` -N.B. `set_orbit` creates an analytical [two-body](https://en.wikipedia.org/wiki/Two-body_problem) orbit. Perturbations such as Earth oblateness (J2) and atmospheric drag are not modelled, so propagated positions deviate from real satellite trajectories as the propagation horizon grows — for a satellite in low Earth orbit, typically hundreds of kilometers within a few hours and thousands of kilometers within a few days relative to the corresponding SGP4/TLE trajectory. If your orbit data comes from a TLE, prefer `set_TLE` below. +N.B. `set_orbit` creates an analytical [two-body](https://en.wikipedia.org/wiki/Two-body_problem) orbit. Perturbations such as Earth oblateness (J2) and atmospheric drag are not modelled, so propagated positions deviate from real satellite trajectories as the propagation horizon grows — for a satellite in low Earth orbit, typically tens of kilometers within the first few hours, hundreds within a day and thousands within a few days relative to the corresponding SGP4/TLE trajectory. If your orbit data comes from a TLE, prefer `set_TLE` below. ##### SGP4 / Two-line element (TLE) For using SGP4 / [Two-line element (TLE)](https://en.wikipedia.org/wiki/Two-line_element_set) you need to specify the TLE of the [SpacecraftActor](#spacecraftactor). In this case, we will use the TLE of the [Sentinel-2A](https://en.wikipedia.org/wiki/Sentinel-2) satellite from [celestrak](https://celestrak.com/). ```py +import pykep as pk from paseos import ActorBuilder, SpacecraftActor # Define an actor of type SpacecraftActor sat_actor = ActorBuilder.get_actor_scaffold(name="Sentinel-2A", @@ -327,7 +329,7 @@ You can access the orbit of a [SpacecraftActor](#spacecraftactor) with ```py # Position, velocity and altitude can be accessed like this -t0 = pk.epoch("2022-06-16 00:00:00.000") # Define the time (epoch) +t0 = pk.epoch_from_string("2022-06-16 00:00:00.000") # Define the time (epoch) print(sat_actor.get_position(t0)) print(sat_actor.get_position_velocity(t0)) print(sat_actor.get_altitude(t0)) @@ -366,6 +368,14 @@ from paseos import ActorBuilder, SpacecraftActor sat_actor = ActorBuilder.get_actor_scaffold(name="mySat", actor_type=SpacecraftActor, epoch=pk.epoch(0)) + +# Solar panels need a central body to determine eclipses, so set an orbit first. +ActorBuilder.set_orbit(actor=sat_actor, + position=[10000000, 0, 0], + velocity=[0, 8000.0, 0], + epoch=pk.epoch(0), + central_body=pk.planet.jpl_lp("earth")) + # Add a power device ActorBuilder.set_power_devices(actor=sat_actor, battery_level_in_Ws=100, # current level @@ -408,8 +418,17 @@ The following parameters have to be specified for this: To use it, simply equip your [SpacecraftActor](#spacecraftactor) with a thermal model with: ```py +import pykep as pk from paseos import SpacecraftActor, ActorBuilder my_actor = ActorBuilder.get_actor_scaffold("my_actor", SpacecraftActor, pk.epoch(0)) + +# The thermal model needs a central body for albedo and IR flux, so set an orbit first. +ActorBuilder.set_orbit(actor=my_actor, + position=[10000000, 0, 0], + velocity=[0, 8000.0, 0], + epoch=pk.epoch(0), + central_body=pk.planet.jpl_lp("earth")) + ActorBuilder.set_thermal_model( actor=my_actor, actor_mass=50.0, # Setting mass to 50kg @@ -441,6 +460,7 @@ PASEOS models three types of radiation effects. You can add a radiation model affecting the operations of the devices you are interested in with ```py + import pykep as pk from paseos import SpacecraftActor, ActorBuilder my_actor = ActorBuilder.get_actor_scaffold("my_actor", SpacecraftActor, pk.epoch(0)) ActorBuilder.set_radiation_model( @@ -463,7 +483,7 @@ To get a binary mask to model data corruption on the [local actor](#local-actor) ```py mask = paseos_instance.model_data_corruption(data_shape=your_data_shape, - exposure_time_in_s=your_time) + exposure_period_in_s=your_time) ``` #### Custom Modelling @@ -626,7 +646,7 @@ The next code snippet will show how to start the PASEOS simulation with a time d ```py import pykep as pk import paseos -from paseos import ActorBuilder, SpacecraftActor +from paseos import ActorBuilder, SpacecraftActor, load_default_cfg #Define today as pykep epoch (16-06-22) #please, refer to https://esa.github.io/pykep/documentation/core.html#pykep.epoch @@ -652,23 +672,28 @@ ActorBuilder.set_orbit( cfg=load_default_cfg() # Set simulation starting time by converting epoch to seconds cfg.sim.start_time=today.mjd2000 * pk.DAY2SEC -# initialize PASEOS simulation -sim = paseos.init_sim(local_actor) +# initialize PASEOS simulation with the modified cfg +sim = paseos.init_sim(local_actor, cfg) ``` -You can access the current simulation time (seconds since the start) and the current epoch like this: +You can access the current simulation time and the current epoch like this: ```py -time_since_start_in_s = sim.simulation_time +simulation_time_in_s = sim.simulation_time current_epoch = sim.local_time ``` +N.B. `sim.simulation_time` counts from `cfg.sim.start_time`, which defaults to 0 at MJD2000. With the default cfg it is therefore seconds since MJD2000, not seconds since your simulation started. Subtract `cfg.sim.start_time` if you want elapsed time. + #### Faster than real-time execution In some cases, you may be interested to simulate your spacecraft operating for an extended period. By default, PASEOS operates in real-time, thus this would take a lot of time. However, you can increase the rate of time passing (i.e. the spacecraft moving, power being charged / consumed etc.) using the `time_multiplier` parameter. Set it as follows when initializing PASEOS. ```py +import paseos +from paseos import load_default_cfg + cfg = load_default_cfg() # loading cfg to modify defaults cfg.sim.time_multiplier = 10 # setting the parameter so that in 1s real time, paseos models 10s having passed paseos_instance = paseos.init_sim(my_local_actor, cfg) # initialize paseos instance @@ -682,7 +707,7 @@ Alternatively, you can rely on an event-based mode where PASEOS will simulate th ```py import pykep as pk import paseos - from paseos import ActorBuilder, SpacecraftActor + from paseos import ActorBuilder, SpacecraftActor, load_default_cfg # Define the central body as Earth by using pykep APIs. earth = pk.planet.jpl_lp("earth") @@ -881,6 +906,19 @@ ActorBuilder.set_power_devices(actor=local_actor, # Charging rate in W charging_rate_in_W=10) +# The constraint below reads the actor temperature, so a thermal model is required. +ActorBuilder.set_thermal_model( + actor=local_actor, + actor_mass=50.0, + actor_initial_temperature_in_K=273.15, + actor_sun_absorptance=1.0, + actor_infrared_absorptance=1.0, + actor_sun_facing_area=1.0, + actor_central_body_facing_area=1.0, + actor_emissive_area=1.0, + actor_thermal_capacity=1000, +) + # initialize PASEOS simulation sim = paseos.init_sim(local_actor) @@ -982,7 +1020,7 @@ sim.perform_activity("activity_A_with_termination_function", #### Visualization -Navigate to paseos/visualization to find a jupyter notebook containing examples of how to visualize PASEOS. +Navigate to [examples/visualization](examples/visualization) to find a jupyter notebook containing examples of how to visualize PASEOS. Visualization can be done in interactive mode or as an animation that is saved to your disc. In the figure below, Earth is visualized in the centre as a blue sphere with different spacecraft in orbit. Each spacecraft has a name and if provided, a battery level and a communications device. @@ -1014,8 +1052,14 @@ state_of_charge = instance.monitor["state_of_charge"] To evaluate your results, you will likely want to track the operational parameters, such as actor battery status, currently running activity etc. of actors over the course of your simulation. By default, PASEOS will log the current actor status every 10 seconds, however you can change that rate by editing the default configuration, as explained in [How to use the cfg](#how-to-use-the-cfg). You can save the current log to a \*.csv file at any point. ```py +import paseos +from paseos import load_default_cfg + cfg = load_default_cfg() # loading cfg to modify defaults -cfg.io.logging_interval = 0.25 # log every 0.25 seconds +# Log every 0.25s. The interval is checked once per physics timestep, so it cannot be +# finer than cfg.sim.dt - lower dt as well if you want a sub-second logging rate. +cfg.sim.dt = 0.25 +cfg.io.logging_interval = 0.25 paseos_instance = paseos.init_sim(my_local_actor, cfg) # initialize paseos instance # Performing activities, running the simulation (...) diff --git a/paseos/__init__.py b/paseos/__init__.py index ef5e38a..7e3e3ff 100644 --- a/paseos/__init__.py +++ b/paseos/__init__.py @@ -30,7 +30,8 @@ def init_sim(local_actor: BaseActor, cfg: DotMap = None, starting_epoch: pk.epoc cfg (DotMap, optional): Configuration file. If None, default configuration will be used. Defaults to None. starting_epoch(pk.epoch): Starting epoch of the simulation. Will override cfg and local actor one. Returns: - PASEOS: Instance of the simulation (only one can exist, singleton) + PASEOS: Instance of the simulation. One instance per actor you wish to model; + several can coexist in the same process. """ logger.debug("Initializing simulation.") if cfg is None: From 555d329e6fb740280d2bed00fc72f3ec3b023c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20G=C3=B3mez?= Date: Sat, 22 Aug 2026 13:09:53 +0200 Subject: [PATCH 2/3] docs: fix time_multiplier and mesh line-of-sight snippets check_cfg validates float entries strictly, so time_multiplier needs 10.0 rather than 10, and is_in_line_of_sight takes a required epoch. Both were briefly going to be changed on the library side instead; fixing the snippets is the right way round. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c3773f8..096ce92 100644 --- a/README.md +++ b/README.md @@ -587,7 +587,7 @@ ActorBuilder.set_central_body(my_sat, comet, (mesh_points, mesh_triangles)) # Below computations will now use the mesh instead spherical approximations print(my_sat.is_in_eclipse()) -print(my_sat.is_in_line_of_sight(some_other_actor)) +print(my_sat.is_in_line_of_sight(some_other_actor, epoch)) # You could even specify a rotation of the central body. # Set a rotation period of 1 second around the z axis @@ -695,7 +695,7 @@ import paseos from paseos import load_default_cfg cfg = load_default_cfg() # loading cfg to modify defaults -cfg.sim.time_multiplier = 10 # setting the parameter so that in 1s real time, paseos models 10s having passed +cfg.sim.time_multiplier = 10.0 # setting the parameter so that in 1s real time, paseos models 10s having passed paseos_instance = paseos.init_sim(my_local_actor, cfg) # initialize paseos instance ``` From 984e2f07df617234254051b4e464905afa458fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20G=C3=B3mez?= Date: Sat, 22 Aug 2026 13:28:58 +0200 Subject: [PATCH 3/3] docs: correct docstrings and links flagged in review - set_orbit and simulation_time docstrings carried the same wrong claims this PR corrects in the README prose. They are what users see via help() and autodoc, so the repo would otherwise contradict itself. - simulation_time is cfg.sim.start_time plus elapsed, and start_time is not 0 by default: init_sim overwrites it with the local actor's epoch when no cfg is passed. Reworded the N.B. accordingly. - The visualization notebook link is now absolute. This README is the PyPI long_description and is included into the Sphinx docs, where a repo-relative link 404s. - my_local_actor is never defined anywhere in the README, so two snippets still could not run as printed; both now use the '(...) # actor definition etc., see above' convention already in use. - The mesh snippet used pickle and np without importing them. --- README.md | 11 +++++++++-- paseos/actors/actor_builder.py | 8 ++++---- paseos/paseos.py | 8 ++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 096ce92..a2aabf3 100644 --- a/README.md +++ b/README.md @@ -560,6 +560,9 @@ We assume `polyhedral_propagator` to be a custom propagator as explained in [Cus To correctly compute eclipses, we also need to know the orbit of the custom central body around the Sun. In this case we use the [orbital elements](https://en.wikipedia.org/wiki/Orbital_elements) one [can find online for 67P/Churyumov–Gerasimenko](https://en.wikipedia.org/wiki/67P/Churyumov–Gerasimenko). ```py +import pickle + +import numpy as np import pykep as pk from paseos import ActorBuilder, SpacecraftActor @@ -683,7 +686,7 @@ simulation_time_in_s = sim.simulation_time current_epoch = sim.local_time ``` -N.B. `sim.simulation_time` counts from `cfg.sim.start_time`, which defaults to 0 at MJD2000. With the default cfg it is therefore seconds since MJD2000, not seconds since your simulation started. Subtract `cfg.sim.start_time` if you want elapsed time. +N.B. `sim.simulation_time` is `cfg.sim.start_time` plus the elapsed simulation time, not time since your simulation started. Since `start_time` is normally derived from an epoch (`init_sim` uses the local actor's epoch when you pass no cfg), it usually reads as seconds since MJD2000. Subtract `cfg.sim.start_time` if you want elapsed time. #### Faster than real-time execution @@ -694,6 +697,8 @@ In some cases, you may be interested to simulate your spacecraft operating for a import paseos from paseos import load_default_cfg +(...) # actor definition etc., see above + cfg = load_default_cfg() # loading cfg to modify defaults cfg.sim.time_multiplier = 10.0 # setting the parameter so that in 1s real time, paseos models 10s having passed paseos_instance = paseos.init_sim(my_local_actor, cfg) # initialize paseos instance @@ -1020,7 +1025,7 @@ sim.perform_activity("activity_A_with_termination_function", #### Visualization -Navigate to [examples/visualization](examples/visualization) to find a jupyter notebook containing examples of how to visualize PASEOS. +Navigate to [examples/visualization/example_jupyter.ipynb](https://github.com/aidotse/PASEOS/blob/main/examples/visualization/example_jupyter.ipynb) to find a jupyter notebook containing examples of how to visualize PASEOS. Visualization can be done in interactive mode or as an animation that is saved to your disc. In the figure below, Earth is visualized in the centre as a blue sphere with different spacecraft in orbit. Each spacecraft has a name and if provided, a battery level and a communications device. @@ -1055,6 +1060,8 @@ To evaluate your results, you will likely want to track the operational paramete import paseos from paseos import load_default_cfg +(...) # actor definition etc., see above + cfg = load_default_cfg() # loading cfg to modify defaults # Log every 0.25s. The interval is checked once per physics timestep, so it cannot be # finer than cfg.sim.dt - lower dt as well if you want a sub-second logging rate. diff --git a/paseos/actors/actor_builder.py b/paseos/actors/actor_builder.py index 9fcff96..0ad6899 100644 --- a/paseos/actors/actor_builder.py +++ b/paseos/actors/actor_builder.py @@ -273,10 +273,10 @@ def set_orbit( are not modelled by this orbit, so propagated positions increasingly deviate from real satellite trajectories as the propagation horizon grows. For a satellite in low Earth orbit, the deviation from the corresponding SGP4/TLE - trajectory typically reaches hundreds of kilometers within a few hours and - thousands of kilometers within a few days. If your position / velocity come - from a TLE, use set_TLE instead to propagate with SGP4. For higher-fidelity - propagators, use set_custom_orbit. + trajectory is typically tens of kilometers within the first few hours, + hundreds within a day and thousands within a few days. If your position / + velocity come from a TLE, use set_TLE instead to propagate with SGP4. + For higher-fidelity propagators, use set_custom_orbit. Args: actor (BaseActor): The actor to define on diff --git a/paseos/paseos.py b/paseos/paseos.py index b9e4fe1..7d22a0e 100644 --- a/paseos/paseos.py +++ b/paseos/paseos.py @@ -239,10 +239,14 @@ def model_data_corruption(self, data_shape: list, exposure_period_in_s: float): @property def simulation_time(self) -> float: - """Get the current simulation time of this paseos instance in seconds since start. + """Get the current simulation time of this paseos instance. + + This is cfg.sim.start_time plus the elapsed simulation time, not elapsed + time on its own -- for an epoch-derived start time it reads as seconds + since MJD2000. Returns: - float: Time since start in seconds. + float: Simulation time in seconds. """ return self._state.time