Related: C API Guide | Component Authoring
The Icarus Python API provides a Pythonic interface to the simulation framework with full numpy integration. It's ideal for interactive analysis, Monte Carlo campaigns, and scripting.
Installation
The Python bindings are built with the --python or --all-interfaces flag:
./scripts/build.sh --python
Then add the build directory to your Python path:
export PYTHONPATH=/path/to/icarus/build/python:$PYTHONPATH
Or in Python:
import sys
sys.path.insert(0, "/path/to/icarus/build/python")
import icarus
Quick Start
import icarus
sim.stage()
while sim.time < sim.end_time:
print(f"t={sim.time:.2f}, alt={sim['Vehicle.position.z']:.1f}")
sim.step()
Top-level simulation coordinator.
Definition Simulator.hpp:70
API Reference
Module Attributes
import icarus
icarus.__version__
icarus.version_info
Exception Classes
try:
print(f"Config error: {e}")
try:
sim["NonExistent.Signal"]
print("Signal not found")
Configuration/parsing errors with optional file context.
Definition Error.hpp:185
Lifecycle Enum
from icarus import Lifecycle
Lifecycle.UNINITIALIZED
Lifecycle.PROVISIONED
Lifecycle.STAGED
Lifecycle.RUNNING
Simulator Class
Constructor
Create simulator from YAML configuration file. Raises ConfigError on failure.
Lifecycle Methods
sim.stage()
sim.step()
sim.step(0.005)
sim.reset()
Properties
| Property | Type | Description |
| sim.lifecycle | Lifecycle | Current lifecycle state |
| sim.time | float | Current simulation time (MET) in seconds |
| sim.dt | float | Configured timestep in seconds |
| sim.end_time | float | Configured end time in seconds |
| sim.name | str | Simulation name from config |
| sim.flight_phase | int | Current flight phase value |
| sim.flight_phase_name | str | Current flight phase name |
Signal Access
Signals can be accessed by name using either method syntax or dictionary syntax:
altitude = sim.get("Vehicle.position.z")
altitude = sim["Vehicle.position.z"]
sim.set("Vehicle.position.z", 1000.0)
sim["Vehicle.position.z"] = 1000.0
Signal names use dot notation: "Component.signal.axis".
State Vector (Numpy Integration)
The state vector provides high-performance bulk access for:
- Monte Carlo IC perturbation
- External integrators
- Checkpointing / warmstart
- High-frequency logging
Note: States ARE signals in Icarus. Every state is also accessible via sim["name"]. The state vector is an optimization for bulk operations.
import numpy as np
state = sim.state
print(f"State size: {sim.state_size}")
state = sim.state.copy()
state[0] += 1.0
sim.state = state
names = sim.state_names
print(f"State[0] is: {names[0]}")
Introspection
signals = sim.signals
print(f"Total signals: {sim.signal_count}")
schema = sim.schema_json
print(f"Components: {len(schema['components'])}")
print(f"Total outputs: {schema['summary']['total_outputs']}")
data = sim.to_dict()
print(f"Altitude: {data['Vehicle.position.z']}")
Utility Methods
run_until
sim.run_until(end_time: float, callback: Callable = None)
Run simulation until end_time. Optional callback is called after each step.
sim.run_until(10.0)
times = []
altitudes = []
def record(s):
times.append(s.time)
altitudes.append(s["Vehicle.position.z"])
sim.run_until(10.0, record)
compute_derivatives (Expert)
xdot = sim.compute_derivatives(t: float)
Compute state derivatives at time t. For external integrators.
Usage Patterns
Basic Simulation Loop
import icarus
sim.stage()
data = {"time": [], "altitude": [], "velocity": []}
while sim.time < sim.end_time:
data["time"].append(sim.time)
data["altitude"].append(sim["Vehicle.position.z"])
data["velocity"].append(sim["Vehicle.velocity.z"])
sim.step()
import matplotlib.pyplot as plt
plt.plot(data["time"], data["altitude"])
plt.xlabel("Time (s)")
plt.ylabel("Altitude (m)")
plt.show()
Monte Carlo Analysis
import icarus
import numpy as np
def run_monte_carlo(config_path, num_runs=100, seed=42):
rng = np.random.default_rng(seed)
results = []
for i in range(num_runs):
sim.stage()
state = sim.state.copy()
state += rng.normal(0, 0.01, size=state.shape)
sim.state = state
sim.run_until(sim.end_time)
results.append({
"final_altitude": sim["Vehicle.position.z"],
"final_velocity": sim["Vehicle.velocity.z"],
})
return results
results = run_monte_carlo("config/ballistic.yaml", num_runs=1000)
import pandas as pd
df = pd.DataFrame(results)
print(df.describe())
Parallel Monte Carlo with multiprocessing
import icarus
import numpy as np
from multiprocessing import Pool
def run_single(args):
config_path, seed = args
rng = np.random.default_rng(seed)
sim.stage()
state = sim.state.copy()
state += rng.normal(0, 0.01, size=state.shape)
sim.state = state
sim.run_until(sim.end_time)
return sim["Vehicle.position.z"]
if __name__ == "__main__":
config = "config/ballistic.yaml"
seeds = range(1000)
with Pool() as pool:
results = pool.map(run_single, [(config, s) for s in seeds])
print(f"Mean altitude: {np.mean(results):.2f}")
print(f"Std deviation: {np.std(results):.2f}")
Checkpointing / Warmstart
import icarus
import numpy as np
sim.stage()
sim.run_until(10.0)
checkpoint_state = sim.state.copy()
checkpoint_time = sim.time
print(f"Checkpoint at t={checkpoint_time}")
sim.run_until(20.0)
print(f"Final altitude: {sim['Vehicle.position.z']:.1f}")
sim.reset()
sim.state = checkpoint_state
sim["Vehicle.force.z"] = 1000.0
sim.run_until(20.0)
print(f"With thrust: {sim['Vehicle.position.z']:.1f}")
Interactive Exploration (Jupyter)
import icarus
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import clear_output
sim.stage()
fig, ax = plt.subplots()
for _ in range(100):
sim.step()
clear_output(wait=True)
ax.clear()
ax.set_title(f"t = {sim.time:.2f} s")
ax.bar(["x", "y", "z"], [
sim["Vehicle.position.x"],
sim["Vehicle.position.y"],
sim["Vehicle.position.z"]
])
plt.pause(0.01)
Sensitivity Analysis
import icarus
import numpy as np
def run_with_param(config, param_name, param_value):
sim.stage()
sim[param_name] = param_value
sim.run_until(sim.end_time)
return sim["Vehicle.position.z"]
masses = np.linspace(0.5, 2.0, 20)
altitudes = [run_with_param("config.yaml", "Vehicle.mass", m) for m in masses]
import matplotlib.pyplot as plt
plt.plot(masses, altitudes)
plt.xlabel("Mass (kg)")
plt.ylabel("Final Altitude (m)")
plt.title("Sensitivity to Mass")
plt.show()
Integration with Scientific Python
NumPy
import numpy as np
state = sim.state
print(f"Shape: {state.shape}, dtype: {state.dtype}")
state = np.clip(state, -100, 100)
sim.state = state
Pandas
import pandas as pd
data = []
sim.stage()
while sim.time < sim.end_time:
row = {"time": sim.time}
row.update(sim.to_dict())
data.append(row)
sim.step()
df = pd.DataFrame(data)
df.to_csv("results.csv")
SciPy (External Integration)
import icarus
import numpy as np
from scipy.integrate import solve_ivp
sim.stage()
def dynamics(t, y):
sim.state = y
return sim.compute_derivatives(t)
sol = solve_ivp(
dynamics,
[0, sim.end_time],
sim.state.copy(),
method='RK45',
max_step=sim.dt
)
print(f"Final state: {sol.y[:, -1]}")
Error Handling
import icarus
try:
print(f"Failed to load config: {e}")
sim.stage()
try:
value = sim["NonExistent.Signal"]
print("Signal not found")
try:
sim.step()
print("Must stage before stepping")
Lifecycle ordering/state errors.
Definition Error.hpp:236
Best Practices
- Copy state before modifying: state = sim.state.copy() to avoid unintended side effects
- Use run_until for simple runs: More efficient than manual loops
- Prefer sim["name"] syntax: More Pythonic than sim.get("name")
- Use multiprocessing for Monte Carlo: Each process gets its own simulator
- Check lifecycle state: sim.lifecycle == Lifecycle.STAGED before operations
See Also