Skip to content

Latest commit

 

History

History
435 lines (337 loc) · 12.4 KB

File metadata and controls

435 lines (337 loc) · 12.4 KB

MASTER DATA MODEL: Strict Contract

Status: Core architecture principle enforced in models/core.py
Last Updated: February 22, 2026
Authority: Kernel-centric architecture (single state mutation point)


Executive Summary

The MASTER DATA MODEL defines immutable state containers that form the complete contract between the kernel and all physics modules.

Key Principle: The kernel is the single authority for state. Physics modules are pure functions.

Kernel (state manager) ← → Physics Modules (pure functions)
        ↑
        └── Physics modules NEVER communicate directly
        └── All interaction flows ONLY through kernel

Data Containers (Immutable)

1. SpacecraftState

The complete spacecraft state snapshot. Every physics module receives this.

@dataclass(frozen=True)
class SpacecraftState:
    # PRIMARY DYNAMICS
    position: Vector3              # [m] inertial frame
    velocity: Vector3              # [m/s] inertial frame
    
    # ATTITUDE
    attitude_quaternion: Quaternion # [q0, q1, q2, q3] normalized
    angular_velocity: Vector3       # [rad/s] inertial frame
    
    # SPACECRAFT PROPERTIES
    mass: float                     # [kg] total spacecraft mass
    power_available: float          # [W] available electrical power
    
    # INTERNAL STATES
    thermal_state: Dict[str, float] # [K] subsystem temperatures
    structural_modes: Tuple[float]  # structural deformation modes
    
    # PROPULSION
    propulsion: PropulsionState     # thrust capability
    
    # METADATA
    epoch_sec: float                # [s] absolute time since J2000
    mission_elapsed_sec: float      # [s] time since mission start
    valid: bool                     # state is physically valid

Interface Contract:

  • All vectors in inertial frame unless noted
  • All quantities in SI units
  • Frozen (immutable) to prevent accidental mutations
  • Kernel is the only component that creates new SpacecraftState instances

Backward Compatibility: The implementation maintains references (r, v, q, omega) to old field names for legacy code compatibility.


2. EnvironmentState

The external environment affecting the spacecraft. Populated by environment models (solar cycle, CME, radiation).

@dataclass(frozen=True)
class EnvironmentState:
    solar_flux: float               # [W/m²] solar irradiance
    magnetic_field: Vector3         # [T] inertial frame field
    solar_wind_density: float       # [kg/m³] plasma density
    radiation_index: float          # [0-10] radiation belt (Kp-like)
    epoch_sec: float                # [s] absolute time

Responsibility Chain:

  1. Environment models (solar_cycle.py, cme.py, radiation_belts.py) compute conditions
  2. Kernel packages these into EnvironmentState
  3. All physics modules receive the same EnvironmentState snapshot
  4. Result: Consistent environment across all modules for a given time step

3. PropulsionState

Propulsion system capability and performance.

@dataclass(frozen=True)
class PropulsionState:
    thrust_vector: Vector3          # [N] thrust in inertial frame
    mass_flow_rate: float           # [kg/s] propellant consumption
    efficiency: float               # [0-1] thruster efficiency
    fuel_remaining_kg: float        # [kg] remaining propellant

Management:

  • Part of SpacecraftState
  • Updated by propulsion module or GNC logic
  • Propagated through engine for fuel tracking

4. StateDerivative

The output of physics computation. Represents dr/dt, dv/dt, etc.

@dataclass(frozen=True)
class StateDerivative:
    velocity_derivative: Vector3    # dr/dt = v [m/s]
    acceleration: Vector3           # dv/dt [m/s²]
    angular_acceleration: Vector3   # dω/dt [rad/s²]
    power_derivative: float         # dP/dt [W]
    mass_derivative: float          # dm/dt [kg/s]
    thermal_derivatives: Dict[str]  # dT/dt [K/s] per subsystem

Integration:

  • ODE solver combines StateDerivatives from all modules
  • Kernel advances state: state_new = state_old + dt * combined_derivatives
  • Physics modules never see intermediate updates

Physics Module Interface

Every physics module implements this strict contract:

class PhysicsModule(ABC):
    """All modules MUST implement this interface."""
    
    def compute(self, physics_input: PhysicsInput) -> PhysicsOutput:
        """Pure function: (state, environment, config) → output"""
        pass

PhysicsInput: What modules receive

@dataclass(frozen=True)
class PhysicsInput:
    state: SpacecraftState          # complete immutable state snapshot
    environment: EnvironmentState   # consistent environment snapshot
    t_epoch: float                  # absolute time [s]
    params: Dict[str, Any]          # module-specific configuration

Constraint: Input is immutable. Module cannot modify it.

PhysicsOutput: What modules return

@dataclass(frozen=True)
class PhysicsOutput:
    state_derivative: StateDerivative  # change in state (for integration)
    diagnostics: Dict[str, float]      # scalar measurements (log these)
    events: List[Event]                # discrete occurrences detected
    valid: bool                        # computation succeeded

Constraint: No side effects. Output is purely functional.


Composability Pattern

How the kernel orchestrates physics modules:

Step t:
├─ state_t = current spacecraft state (immutable)
├─ environment_t = current environment (immutable)
│
├─ For each physics module M:
│  ├─ input = PhysicsInput(state_t, environment_t, t, params_M)
│  ├─ output_M = M.compute(input)
│  └─ collect output_M
│
├─ Combine all state_derivatives
│  └─ combined = sum of all module derivatives
│
└─ ODE integration:
   └─ state_{t+dt} = integrate(state_t, combined, dt)
     └─ state_{t+dt} is a NEW immutable SpacecraftState

Advantages:

  1. Composable: Add/remove modules without code changes
  2. Testable: Each module tested independently
  3. Deterministic: Same input → identical output (with seed)
  4. Parallelizable: All modules can run in parallel (read-only state)
  5. Auditable: Complete state history, no hidden mutations

Strict Contract Enforcement

1. Immutability (Language Level)

All data containers use @dataclass(frozen=True):

try:
    state.position = (1.0, 2.0, 3.0)  # AttributeError!
except FrozenInstanceError:
    print("Cannot mutate frozen dataclass")

2. Type Safety (Compile Time)

# This is a type error (mypy will catch it):
output: PhysicsOutput = module.compute(input)
output.state_derivative = new_derivative  # Type mismatch!

# Correct usage:
output = PhysicsOutput(
    state_derivative=new_derivative,
    diagnostics={...},
    events=[...],
    valid=True
)

3. Kernel Authority (Runtime)

The kernel enforces that modules cannot create new SpacecraftState instances:

# In kernel/engine.py
def _integrate_step(self, state: SpacecraftState, dt: float):
    # 1. Get module outputs
    outputs = [m.compute(PhysicsInput(...)) for m in self.modules]
    
    # 2. Combine derivatives (modules cannot interfere)
    derivatives = [out.state_derivative for out in outputs]
    combined = self._combine_derivatives(derivatives)
    
    # 3. KERNEL creates new state (modules never do this)
    new_state = self._advance_state(state, combined, dt)
    
    return new_state  # Return new immutable snapshot

Enforcement: Modules receive SpacecraftState but cannot instantiate new ones.


Example: Two-Body Orbital Mechanics Module

from models.core import (
    PhysicsModule, PhysicsInput, PhysicsOutput, 
    StateDerivative, PhysicsModuleType
)

class TwoBodyModule(PhysicsModule):
    """Orbital mechanics: r̈ = -μ/r³ * r"""
    
    def __init__(self, mu: float):
        self.mu = mu  # gravitational parameter [m³/s²]
    
    def compute(self, input: PhysicsInput) -> PhysicsOutput:
        # Read immutable inputs
        pos = input.state.position
        vel = input.state.velocity
        
        # Compute gravitational acceleration
        r_mag = (pos[0]**2 + pos[1]**2 + pos[2]**2) ** 0.5
        a_mag = self.mu / (r_mag ** 3)
        accel = (-a_mag * pos[0], -a_mag * pos[1], -a_mag * pos[2])
        
        # Construct derivative (immutable)
        derivative = StateDerivative(
            velocity_derivative=vel,           # dr/dt = v
            acceleration=accel,                # dv/dt = a
            angular_acceleration=(0, 0, 0),    # no torque
        )
        
        # Return output (immutable)
        return PhysicsOutput(
            state_derivative=derivative,
            diagnostics={"r_mag_m": r_mag},
            events=[],  # no special events
            valid=True
        )
    
    def get_module_type(self) -> PhysicsModuleType:
        return PhysicsModuleType.ORBITAL_MECHANICS

Key Points:

  • Module receives immutable PhysicsInput
  • Reads freely from state, environment, params
  • Computes new StateDerivative (doesn't modify input)
  • Returns immutable PhysicsOutput
  • Kernel integrates the derivative to produce new state

Migration Guide (Old → New)

Old Pattern (Deprecated)

# Old: PhysicsInput with optional environment dict
class PhysicsInput:
    state: SpacecraftState
    t_epoch: float
    params: Dict
    environment: Optional[Dict[str, float]] = None  # ❌ untyped

# Old: PhysicsOutput with separate fields
class PhysicsOutput:
    acceleration: Vector3
    torque: Vector3
    diagnostics: Dict
    state_derivative: Optional[Dict] = None
    valid: bool

New Pattern (Strict Contract)

# New: EnvironmentState is a formal data class
class PhysicsInput:
    state: SpacecraftState
    environment: EnvironmentState  # ✓ typed, structured
    t_epoch: float
    params: Dict

# New: StateDerivative is explicit
class PhysicsOutput:
    state_derivative: StateDerivative  # ✓ complete derivative
    diagnostics: Dict
    events: List[Event]
    valid: bool

Updating Your Module

# 1. Update compute() signature (old → new)
def compute(self, input: PhysicsInput) -> PhysicsOutput:
    # 2. Access environment as structured data
    solar_flux = input.environment.solar_flux  # typed
    
    # 3. Create StateDerivative explicitly
    derivative = StateDerivative(
        velocity_derivative=vel,
        acceleration=accel,
        angular_acceleration=ang_accel,
        power_derivative=power_rate,
        # ... other derivatives
    )
    
    # 4. Return PhysicsOutput
    return PhysicsOutput(
        state_derivative=derivative,
        diagnostics=diags,
        events=events,
        valid=True
    )

Testing the Contract

Test 1: Immutability

def test_immutability():
    state = SpacecraftState(
        position=(1, 2, 3),
        velocity=(4, 5, 6),
        attitude_quaternion=(1, 0, 0, 0),
        angular_velocity=(0, 0, 0),
        mass=1000,
        power_available=500,
        thermal_state={},
        structural_modes=()
    )
    
    # Cannot mutate
    with pytest.raises(FrozenInstanceError):
        state.position = (7, 8, 9)

Test 2: Module Determinism

def test_determinism():
    module = TwoBodyModule(mu=1.327e20)
    input1 = PhysicsInput(state=state, environment=env, t_epoch=0, params={})
    
    output1 = module.compute(input1)
    output2 = module.compute(input1)  # Same input
    
    assert output1.state_derivative.acceleration == output2.state_derivative.acceleration

Test 3: No Side Effects

def test_no_side_effects():
    module = TwoBodyModule(mu=1.327e20)
    original_state = state
    
    module.compute(PhysicsInput(state=original_state, ...))
    
    # State unchanged
    assert original_state.position == (1, 2, 3)
    assert original_state.velocity == (4, 5, 6)

Summary

Aspect Guarantee
State Mutation Kernel only (no modules)
Module Interface (state, env, config) → output
Data Mutability Frozen dataclasses (immutable)
Module Communication No direct; all through kernel
Determinism Same input → identical output
Parallelization All modules can run simultaneously
Testability Each module tested independently
Auditability Complete immutable state history

Result: A production-grade, composable aerospace simulation framework.