| | """ |
| | Archery Tether Propulsion Systems - Main Simulation |
| | ==================================================== |
| | Entry point for the KAPS (Kinetic Active Protection System) simulation. |
| | |
| | This simulation demonstrates: |
| | 1. Cross-formation flight with 4 TABs |
| | 2. 360° defensive bubble mechanics |
| | 3. Slingshot intercept maneuvers |
| | 4. Speed burst escape mechanics |
| | """ |
| |
|
| | import numpy as np |
| | import yaml |
| | import time |
| | from pathlib import Path |
| | from typing import Dict, Optional |
| |
|
| | |
| | from .physics import ( |
| | TetherArray, CableProperties, CableState, |
| | MomentumEngine, SlingshotManeuver, SlingshotParameters, MomentumState, |
| | ReleaseMode |
| | ) |
| |
|
| | |
| | from .entities import ( |
| | MotherDrone, MotherDroneConfig, |
| | TABArray, TowedAerodynamicBody, FormationPosition |
| | ) |
| |
|
| | |
| | from .ai import ( |
| | DefensiveMatrixAI, DefenseConfig, ThreatType, |
| | FormationController, FormationMode |
| | ) |
| |
|
| |
|
| | class KAPSSimulation: |
| | """ |
| | Main simulation controller for the Kinetic Active Protection System. |
| | |
| | Orchestrates: |
| | - Physics integration |
| | - Entity updates |
| | - AI decision making |
| | - Visualization callbacks |
| | """ |
| | |
| | def __init__(self, config_path: Optional[str] = None): |
| | |
| | if config_path: |
| | with open(config_path, 'r') as f: |
| | self.config = yaml.safe_load(f) |
| | else: |
| | self.config = self._default_config() |
| | |
| | |
| | self.momentum_engine = MomentumEngine() |
| | |
| | cable_props = CableProperties( |
| | length=self.config['cables']['length'], |
| | diameter=self.config['cables']['diameter'], |
| | breaking_strength=self.config['cables']['breaking_strength'], |
| | stiffness=self.config['cables']['stiffness'], |
| | damping=self.config['cables']['damping'], |
| | linear_density=self.config['cables']['linear_density'], |
| | max_tension=self.config['cables']['max_tension'], |
| | min_tension=self.config['cables']['min_tension'] |
| | ) |
| | self.tether_array = TetherArray(cable_props, num_cables=4) |
| | |
| | |
| | drone_config = MotherDroneConfig( |
| | mass=self.config['mother_drone']['mass'], |
| | wingspan=self.config['mother_drone']['wingspan'], |
| | max_thrust=self.config['mother_drone']['max_thrust'], |
| | cruise_thrust_ratio=self.config['mother_drone']['cruise_thrust_ratio'] |
| | ) |
| | self.mother_drone = MotherDrone(drone_config) |
| | self.tab_array = TABArray( |
| | cable_length=cable_props.length, |
| | mother_position=self.mother_drone.position |
| | ) |
| | |
| | |
| | self.defensive_ai = DefensiveMatrixAI(DefenseConfig( |
| | detection_radius=self.config['defensive_ai']['detection_radius'], |
| | tracking_update_rate=self.config['defensive_ai']['tracking_update_rate'] |
| | )) |
| | self.formation_ctrl = FormationController() |
| | |
| | |
| | slingshot_params = SlingshotParameters( |
| | spiral_rate=np.radians(self.config['slingshot']['spiral_rate']), |
| | wind_up_time=2.0, |
| | release_angle=np.pi/2, |
| | cable_length=cable_props.length, |
| | conservation_efficiency=self.config['slingshot']['conservation_efficiency'] |
| | ) |
| | self.slingshot = SlingshotManeuver(self.momentum_engine, slingshot_params) |
| | |
| | |
| | self.time = 0.0 |
| | self.dt = self.config['simulation']['timestep'] |
| | self.running = False |
| | |
| | |
| | self.telemetry_log = [] |
| | |
| | def _default_config(self) -> Dict: |
| | """Default configuration if no file provided""" |
| | return { |
| | 'mother_drone': { |
| | 'mass': 150.0, |
| | 'wingspan': 8.0, |
| | 'max_thrust': 2500.0, |
| | 'cruise_thrust_ratio': 0.9 |
| | }, |
| | 'cables': { |
| | 'length': 30.0, |
| | 'diameter': 0.004, |
| | 'breaking_strength': 15000.0, |
| | 'stiffness': 1.2e9, |
| | 'damping': 0.15, |
| | 'linear_density': 0.012, |
| | 'max_tension': 12000.0, |
| | 'min_tension': 50.0 |
| | }, |
| | 'defensive_ai': { |
| | 'detection_radius': 500.0, |
| | 'tracking_update_rate': 100.0 |
| | }, |
| | 'slingshot': { |
| | 'spiral_rate': 45.0, |
| | 'conservation_efficiency': 0.95 |
| | }, |
| | 'simulation': { |
| | 'timestep': 0.001, |
| | 'duration': 60.0 |
| | } |
| | } |
| | |
| | def step(self) -> Dict: |
| | """ |
| | Execute one simulation timestep. |
| | |
| | Returns telemetry data for visualization. |
| | """ |
| | |
| | tab_positions = self.tab_array.get_positions() |
| | tab_velocities = self.tab_array.get_velocities() |
| | |
| | |
| | tether_results = self.tether_array.compute_all_forces( |
| | self.mother_drone.position, |
| | self.mother_drone.velocity, |
| | tab_positions, |
| | tab_velocities, |
| | self.dt |
| | ) |
| | |
| | |
| | formation_commands = self.formation_ctrl.compute_control_commands( |
| | self.mother_drone.position, |
| | self.mother_drone.velocity, |
| | tab_positions, |
| | tab_velocities, |
| | self.dt |
| | ) |
| | |
| | |
| | for tab_id, cmd in formation_commands.items(): |
| | if tab_id in self.tab_array.tabs: |
| | self.tab_array.tabs[tab_id].set_control_targets( |
| | elevator=cmd['elevator'], |
| | rudder=cmd['rudder'] |
| | ) |
| | |
| | |
| | tab_states = { |
| | tab_id: tab.get_status_report() |
| | for tab_id, tab in self.tab_array.tabs.items() |
| | } |
| | defense_response = self.defensive_ai.calculate_defensive_response( |
| | self.mother_drone.position, |
| | self.mother_drone.velocity, |
| | tab_states |
| | ) |
| | |
| | |
| | for release_cmd in defense_response['release_commands']: |
| | tab_id = release_cmd['tab_id'] |
| | if tab_id in self.tab_array.tabs: |
| | tab = self.tab_array.tabs[tab_id] |
| | |
| | |
| | release_vel = self.momentum_engine.compute_release_velocity( |
| | self.mother_drone.momentum_state, |
| | tab.momentum_state, |
| | tether_results.get(tab_id, {}).get('tension', 0), |
| | ReleaseMode.SLINGSHOT if release_cmd['mode'] == 'slingshot' else ReleaseMode.INSTANT, |
| | self.slingshot.params |
| | ) |
| | |
| | |
| | tab.execute_release(release_vel) |
| | self.tether_array.release_cable(tab_id) |
| | self.mother_drone.release_tab(tab_id) |
| | |
| | |
| | total_tether_force = tether_results.get('total_mother_force', np.zeros(3)) |
| | mother_telemetry = self.mother_drone.update(self.dt, total_tether_force) |
| | |
| | |
| | cable_forces = { |
| | tab_id: tether_results.get(tab_id, {}).get('force_on_tab', np.zeros(3)) |
| | for tab_id in self.tab_array.tabs |
| | } |
| | cable_states = { |
| | tab_id: tether_results.get(tab_id, {}).get('state', CableState.ATTACHED) |
| | for tab_id in self.tab_array.tabs |
| | } |
| | tab_telemetry = self.tab_array.update_all(self.dt, cable_forces, cable_states) |
| | |
| | |
| | self.time += self.dt |
| | |
| | |
| | telemetry = { |
| | 'time': self.time, |
| | 'mother_drone': mother_telemetry, |
| | 'tabs': tab_telemetry, |
| | 'cables': { |
| | tab_id: { |
| | 'tension': tether_results.get(tab_id, {}).get('tension', 0), |
| | 'length': tether_results.get(tab_id, {}).get('length', 0), |
| | 'state': str(tether_results.get(tab_id, {}).get('state', CableState.ATTACHED)) |
| | } |
| | for tab_id in self.tab_array.tabs |
| | }, |
| | 'defense': self.defensive_ai.get_defensive_bubble_status(), |
| | 'formation': self.formation_ctrl.get_formation_status( |
| | self.mother_drone.position, |
| | tab_positions |
| | ) |
| | } |
| | |
| | return telemetry |
| | |
| | def run(self, duration: Optional[float] = None, callback=None): |
| | """ |
| | Run simulation for specified duration. |
| | |
| | Args: |
| | duration: Simulation time in seconds (default from config) |
| | callback: Optional function called each step with telemetry |
| | """ |
| | if duration is None: |
| | duration = self.config['simulation']['duration'] |
| | |
| | self.running = True |
| | start_time = time.time() |
| | |
| | print(f"Starting KAPS Simulation - Duration: {duration}s") |
| | print("=" * 50) |
| | |
| | while self.time < duration and self.running: |
| | telemetry = self.step() |
| | |
| | if callback: |
| | callback(telemetry) |
| | |
| | |
| | if int(self.time * 10) % 10 == 0 and int(self.time) != int(self.time - self.dt): |
| | self._print_status(telemetry) |
| | |
| | real_time = time.time() - start_time |
| | print("=" * 50) |
| | print(f"Simulation complete. Sim time: {self.time:.2f}s, Real time: {real_time:.2f}s") |
| | print(f"Speed ratio: {self.time/real_time:.1f}x realtime") |
| | |
| | def _print_status(self, telemetry: Dict): |
| | """Print compact status line""" |
| | md = telemetry['mother_drone'] |
| | defense = telemetry['defense'] |
| | |
| | print(f"T={self.time:6.1f}s | " |
| | f"Alt: {md['altitude']:6.1f}m | " |
| | f"Speed: {md['speed']:5.1f}m/s | " |
| | f"TABs: {self.tab_array.count_attached()}/4 | " |
| | f"Alert: {defense['alert_level']}") |
| | |
| | def inject_threat(self, |
| | position: np.ndarray = None, |
| | velocity: np.ndarray = None, |
| | threat_type: ThreatType = ThreatType.MISSILE_IR): |
| | """ |
| | Inject a threat for testing defensive response. |
| | |
| | Args: |
| | position: Threat starting position (default: 400m ahead) |
| | velocity: Threat velocity vector (default: toward mother drone) |
| | threat_type: Type of threat |
| | """ |
| | if position is None: |
| | |
| | position = self.mother_drone.position + np.array([400, 50, 0]) |
| | |
| | if velocity is None: |
| | |
| | to_mother = self.mother_drone.position - position |
| | to_mother = to_mother / (np.linalg.norm(to_mother) + 1e-9) |
| | velocity = to_mother * 100 |
| | |
| | threat_id = self.defensive_ai.detect_threat( |
| | position, |
| | velocity, |
| | threat_type, |
| | self.mother_drone.position |
| | ) |
| | |
| | print(f"THREAT INJECTED: {threat_id} - {threat_type.value}") |
| | return threat_id |
| | |
| | def execute_speed_burst(self, verbose: bool = False): |
| | """ |
| | Execute emergency speed burst maneuver. |
| | |
| | Releases all TABs for maximum acceleration. |
| | """ |
| | if verbose: |
| | print("EXECUTING SPEED BURST MANEUVER") |
| | |
| | |
| | pre_speed = self.mother_drone.speed |
| | |
| | |
| | released = self.mother_drone.release_all_tabs() |
| | self.tether_array.release_all() |
| | |
| | |
| | for tab in self.tab_array.tabs.values(): |
| | release_vel = self.momentum_engine.compute_release_velocity( |
| | self.mother_drone.momentum_state, |
| | tab.momentum_state, |
| | 0, |
| | ReleaseMode.INSTANT |
| | ) |
| | tab.execute_release(release_vel) |
| | |
| | if verbose: |
| | print(f"Released {released} TABs") |
| | print(f"Pre-release speed: {pre_speed:.1f} m/s") |
| | |
| | |
| | burst_data = self.momentum_engine.compute_mother_acceleration_burst( |
| | self.mother_drone.momentum_state, |
| | [tab.momentum_state for tab in self.tab_array.tabs.values()], |
| | self.mother_drone.current_thrust, |
| | self.mother_drone.compute_parasitic_drag() |
| | ) |
| | |
| | if verbose: |
| | print(f"Expected acceleration multiplier: {burst_data['acceleration_multiplier']:.1f}x") |
| | print(f"Drag reduction: {burst_data['drag_reduction_percent']:.1f}%") |
| | print(f"Predicted speed in 1s: {burst_data['predicted_speed_1s']:.1f} m/s") |
| |
|
| |
|
| | def demo_slingshot_intercept(): |
| | """ |
| | Demonstration: Slingshot intercept of incoming threat. |
| | |
| | Shows the "Archery" mechanics where a TAB is released |
| | on an intercept trajectory. |
| | """ |
| | print("\n" + "=" * 60) |
| | print("DEMO: SLINGSHOT INTERCEPT MANEUVER") |
| | print("=" * 60 + "\n") |
| | |
| | |
| | sim = KAPSSimulation() |
| | |
| | |
| | print("Phase 1: Formation stabilization...") |
| | for _ in range(5000): |
| | sim.step() |
| | |
| | print(f"Formation stable at T={sim.time:.1f}s") |
| | print(f"Mother drone speed: {sim.mother_drone.speed:.1f} m/s") |
| | print(f"TABs attached: {sim.tab_array.count_attached()}") |
| | |
| | |
| | print("\nPhase 2: Threat injection...") |
| | sim.inject_threat( |
| | position=sim.mother_drone.position + np.array([300, 20, -10]), |
| | velocity=np.array([-80, 0, 0]), |
| | threat_type=ThreatType.DRONE_SUICIDE |
| | ) |
| | |
| | |
| | print("\nPhase 3: Defensive response...") |
| | for _ in range(3000): |
| | telemetry = sim.step() |
| | |
| | print(f"\nFinal state at T={sim.time:.1f}s:") |
| | print(f"TABs remaining: {sim.tab_array.count_attached()}") |
| | print(f"Intercepts executed: {sim.defensive_ai.intercepts_executed}") |
| | |
| |
|
| | def demo_speed_burst(): |
| | """ |
| | Demonstration: Speed burst escape maneuver. |
| | |
| | Shows the "mechanical capacitor" effect when all TABs |
| | are released simultaneously. |
| | """ |
| | print("\n" + "=" * 60) |
| | print("DEMO: SPEED BURST MANEUVER") |
| | print("=" * 60 + "\n") |
| | |
| | sim = KAPSSimulation() |
| | |
| | |
| | print("Pre-burst flight...") |
| | for _ in range(2000): |
| | sim.step() |
| | |
| | print(f"T={sim.time:.1f}s - Initiating speed burst") |
| | print(f"Current speed: {sim.mother_drone.speed:.1f} m/s") |
| | |
| | |
| | sim.execute_speed_burst() |
| | |
| | |
| | print("\nPost-burst acceleration...") |
| | for _ in range(2000): |
| | telemetry = sim.step() |
| | if int(sim.time * 100) % 50 == 0: |
| | print(f" T={sim.time:.1f}s - Speed: {telemetry['mother_drone']['speed']:.1f} m/s") |
| | |
| | print(f"\nFinal speed: {sim.mother_drone.speed:.1f} m/s") |
| |
|
| |
|
| | if __name__ == "__main__": |
| | import sys |
| | |
| | if len(sys.argv) > 1: |
| | if sys.argv[1] == "slingshot": |
| | demo_slingshot_intercept() |
| | elif sys.argv[1] == "burst": |
| | demo_speed_burst() |
| | else: |
| | print("Unknown demo. Options: 'slingshot', 'burst'") |
| | else: |
| | |
| | config_path = Path(__file__).parent.parent / "config" / "simulation_params.yaml" |
| | |
| | if config_path.exists(): |
| | sim = KAPSSimulation(str(config_path)) |
| | else: |
| | sim = KAPSSimulation() |
| | |
| | |
| | sim.run(duration=10.0) |
| |
|