Skip to content

Python API Reference

Python bindings for JaguarEngine using pybind11 with full NumPy interoperability.

Installation

From Package (Coming Soon)

pip install jaguar-engine

Build from Source

cd JaguarEngine

# Configure with Python bindings enabled
cmake -B build -DJAGUAR_BUILD_PYTHON=ON

# Build
cmake --build build --parallel

# Install the module
pip install -e .

Requirements: - Python 3.8+ - NumPy (for array interoperability) - pybind11 (auto-fetched by CMake)

Quick Start

import jaguar

# Create and initialize engine
engine = jaguar.Engine()
engine.initialize()

# Create an aircraft
aircraft = engine.create_entity("F-16", jaguar.Domain.Air)

# Set initial state
state = jaguar.EntityState()
state.position = jaguar.Vec3(0, 0, -5000)  # 5 km altitude
state.velocity = jaguar.Vec3(200, 0, 0)    # 200 m/s
state.mass = 12000
engine.set_entity_state(aircraft, state)

# Simulation loop
dt = 0.01
for t in range(10000):  # 100 seconds
    engine.step(dt)

    # Get current state
    state = engine.get_entity_state(aircraft)
    if t % 100 == 0:
        print(f"Time: {t*dt:.1f}s, Alt: {-state.position.z:.0f}m")

engine.shutdown()

Core Types

Vec3

# Construction
v = jaguar.Vec3()           # (0, 0, 0)
v = jaguar.Vec3(1, 2, 3)    # (1, 2, 3)
v = jaguar.Vec3([1, 2, 3])  # From list

# Properties
x, y, z = v.x, v.y, v.z

# Methods
length = v.norm()
unit = v.normalized()
dot = v.dot(other)
cross = v.cross(other)

# Operators
v3 = v1 + v2
v3 = v1 - v2
v3 = v * 2.0
v3 = v / 2.0

# NumPy interop
import numpy as np
arr = v.to_numpy()           # To NumPy array
v = jaguar.Vec3.from_numpy(arr)  # From NumPy array

NumPy Integration

JaguarEngine provides seamless NumPy integration for efficient array operations:

import numpy as np
import jaguar

# Vector batch operations
positions = np.array([
    [0, 0, -1000],
    [100, 0, -1000],
    [200, 0, -1000]
], dtype=np.float64)

# Create Vec3 from NumPy row
v = jaguar.Vec3.from_numpy(positions[0])

# Convert back to NumPy
arr = v.to_numpy()  # [0, 0, -1000]

# Matrix interop
m = jaguar.Mat3x3.Identity()
np_matrix = m.to_numpy()  # 3x3 NumPy array

Quaternion

# Construction
q = jaguar.Quaternion()                    # Identity
q = jaguar.Quaternion(w, x, y, z)
q = jaguar.Quaternion.from_euler(roll, pitch, yaw)
q = jaguar.Quaternion.from_axis_angle(axis, angle)

# Methods
q_norm = q.normalized()
q_conj = q.conjugate()
v_rot = q.rotate(v)
roll, pitch, yaw = q.to_euler()

# Operators
q3 = q1 * q2  # Rotation composition

Mat3x3

# Construction
m = jaguar.Mat3x3()                     # Identity
m = jaguar.Mat3x3.from_euler(r, p, y)

# Access
value = m[i, j]
m[i, j] = value

# Methods
v_out = m * v_in
m_out = m1 * m2
m_t = m.transpose()

# NumPy interop
import numpy as np
arr = np.array(m)        # 3x3 NumPy array
m = jaguar.Mat3x3(arr)   # From NumPy

Engine

class Engine:
    def initialize(self, config: EngineConfig = None) -> bool
    def shutdown(self) -> None

    # Entity management
    def create_entity(self, name: str, domain: Domain) -> int
    def destroy_entity(self, entity_id: int) -> None
    def entity_exists(self, entity_id: int) -> bool

    # State access
    def get_entity_state(self, entity_id: int) -> EntityState
    def set_entity_state(self, entity_id: int, state: EntityState) -> None
    def apply_forces(self, entity_id: int, forces: EntityForces) -> None

    # Environment
    def get_environment(self, entity_id: int) -> Environment
    def get_environment_at(self, position: Vec3) -> Environment

    # Simulation
    def step(self, dt: float) -> None
    def run_for(self, duration: float) -> None

    # Time
    def get_time(self) -> float
    def set_time(self, time: float) -> None

    # Properties
    def entity_count(self) -> int

Example

import jaguar

# Configure engine
config = jaguar.EngineConfig()
config.time_step = 0.01
config.terrain_enabled = True
config.terrain_data_paths = ["/data/dted/"]

# Initialize
engine = jaguar.Engine()
if not engine.initialize(config):
    raise RuntimeError("Failed to initialize engine")

try:
    # Create entities
    aircraft = engine.create_entity("Aircraft", jaguar.Domain.Air)

    # Run simulation
    while engine.get_time() < 60.0:
        engine.step(0.01)

finally:
    engine.shutdown()

EntityState

class EntityState:
    position: Vec3          # m (ECEF)
    velocity: Vec3          # m/s
    orientation: Quaternion
    angular_velocity: Vec3  # rad/s
    mass: float            # kg
    inertia: Mat3x3        # kg·m²

    # Derived properties
    def get_roll(self) -> float    # rad
    def get_pitch(self) -> float   # rad
    def get_yaw(self) -> float     # rad
    def get_euler(self) -> tuple   # (roll, pitch, yaw)

Example

state = jaguar.EntityState()

# Set position (geodetic to ECEF)
lat, lon, alt = 37.0, -122.0, 10000.0
state.position = jaguar.transforms.geodetic_to_ecef(
    lat * jaguar.DEG_TO_RAD,
    lon * jaguar.DEG_TO_RAD,
    alt
)

# Set velocity
state.velocity = jaguar.Vec3(200, 0, 0)

# Set orientation
state.orientation = jaguar.Quaternion.from_euler(
    0.0,                    # roll
    5.0 * jaguar.DEG_TO_RAD, # pitch
    90.0 * jaguar.DEG_TO_RAD # heading (east)
)

# Set mass properties
state.mass = 15000.0
state.inertia = jaguar.Mat3x3()
state.inertia[0, 0] = 20000   # Ixx
state.inertia[1, 1] = 100000  # Iyy
state.inertia[2, 2] = 110000  # Izz

engine.set_entity_state(entity_id, state)

EntityForces

class EntityForces:
    force: Vec3    # N
    torque: Vec3   # N·m

    def clear(self) -> None
    def add_force(self, f: Vec3) -> None
    def add_torque(self, t: Vec3) -> None
    def add_force_at_point(self, f: Vec3, point: Vec3, cg: Vec3) -> None

Example

forces = jaguar.EntityForces()
forces.clear()

# Gravity
gravity = jaguar.Vec3(0, 0, state.mass * jaguar.G0)
forces.add_force(gravity)

# Thrust (in body frame, convert to world)
thrust_body = jaguar.Vec3(50000, 0, 0)
thrust_world = state.orientation.rotate(thrust_body)
forces.add_force(thrust_world)

engine.apply_forces(entity_id, forces)

Environment

class Environment:
    latitude: float        # rad
    longitude: float       # rad
    altitude: float        # m
    position_ecef: Vec3

    atmosphere: AtmosphereState
    terrain: TerrainQuery
    terrain_elevation: float
    ocean: OceanState
    over_water: bool
    gravity: Vec3

class AtmosphereState:
    temperature: float     # K
    pressure: float        # Pa
    density: float         # kg/m³
    speed_of_sound: float  # m/s
    wind: Vec3             # m/s (NED)

class TerrainQuery:
    elevation: float       # m
    normal: Vec3
    slope_angle: float     # rad
    valid: bool

Example

env = engine.get_environment(aircraft)

print(f"Altitude: {env.altitude:.0f} m")
print(f"Temperature: {env.atmosphere.temperature:.1f} K")
print(f"Density: {env.atmosphere.density:.4f} kg/m³")
print(f"Terrain elevation: {env.terrain_elevation:.0f} m")

# Height above terrain
hat = env.altitude - env.terrain_elevation
print(f"Height above terrain: {hat:.0f} m")

Domain Models

Air Domain

from jaguar.domain import air

# Aerodynamics
aero = air.AerodynamicsModel()
aero.set_reference_area(28.0)
aero.set_reference_chord(3.5)
aero.set_reference_span(10.0)

aero.set_elevator(elevator_deg)
aero.compute_forces(state, env, dt, forces)

print(f"CL: {aero.get_cl():.3f}")
print(f"Alpha: {aero.get_alpha() * jaguar.RAD_TO_DEG:.1f} deg")

# Propulsion
engine_model = air.PropulsionModel()
engine_model.set_max_thrust(75000)
engine_model.set_fuel_capacity(3000)
engine_model.set_throttle(0.8)
engine_model.compute_forces(state, env, dt, forces)

Land Domain

from jaguar.domain import land

# Soil
soil = land.SoilProperties.DrySand()

# Terramechanics
terra = land.TerramechanicsModel()
terra.set_contact_area(0.63, 4.6)
terra.set_vehicle_weight(62000 * jaguar.G0)
terra.set_soil(soil)
terra.compute_forces(state, env, dt, forces)

print(f"Sinkage: {terra.get_sinkage():.3f} m")

Sea Domain

from jaguar.domain import sea

# Sea state
sea_state = sea.SeaState.FromNATOSeaState(4)

# Buoyancy
buoyancy = sea.BuoyancyModel()
buoyancy.set_displaced_volume(8390)
buoyancy.set_metacentric_height(2.5)
buoyancy.compute_forces(state, env, dt, forces)

print(f"Draft: {buoyancy.get_draft():.2f} m")
print(f"Heel: {buoyancy.get_heel() * jaguar.RAD_TO_DEG:.1f} deg")

Space Domain

from jaguar.domain import space

# Orbital elements
orbit = space.OrbitalElements()
orbit.semi_major_axis = jaguar.EARTH_RADIUS + 420000
orbit.eccentricity = 0.0001
orbit.inclination = 51.6 * jaguar.DEG_TO_RAD

pos, vel = orbit.to_cartesian()
print(f"Period: {orbit.period() / 60:.1f} minutes")

# SGP4 propagator
tle = space.TLE()
tle.parse(line1, line2)

sgp4 = space.SGP4Propagator()
sgp4.initialize(tle)

for minutes in range(0, 24*60, 10):
    pos_km, vel_kms = sgp4.propagate(minutes)
    pos_m = pos_km * 1000

Coordinate Transforms

from jaguar import transforms

# Geodetic <-> ECEF
ecef = transforms.geodetic_to_ecef(lat_rad, lon_rad, alt_m)
lat, lon, alt = transforms.ecef_to_geodetic(ecef)

# ECEF <-> ECI
eci = transforms.ecef_to_eci(ecef, julian_date)
ecef = transforms.eci_to_ecef(eci, julian_date)

# Julian date
jd = transforms.julian_date(2024, 1, 1, 12, 0, 0.0)

Constants

import jaguar

# Mathematical
jaguar.PI
jaguar.TWO_PI
jaguar.DEG_TO_RAD
jaguar.RAD_TO_DEG

# Physical
jaguar.G0              # Standard gravity (9.80665 m/s²)
jaguar.EARTH_RADIUS    # WGS84 equatorial radius
jaguar.EARTH_MU        # Earth gravitational parameter

# Enumerations
jaguar.Domain.Air
jaguar.Domain.Land
jaguar.Domain.Sea
jaguar.Domain.Space

See Also