================================================================================ Architecture & Design ================================================================================ System architecture, data flow, and design patterns. System Overview =============== .. mermaid:: graph TD MAIN["__main__.py
CLI Entry Point"] CONFIG["config.py
Global Constants"] PARTICLES["particles.py
Taichi Field Mgmt"] SIM["simulation.py
GPU Physics Kernel
2900 lines"] RENDERER["renderer.py
Taichi UI & ImGui"] PDG["pdg_table.py
Particle Catalog"] DATALOADER["data_loader.py
HDF5/CSV Import"] MAIN --> CONFIG MAIN --> SIM MAIN --> RENDERER MAIN --> PDG MAIN --> PARTICLES SIM --> CONFIG SIM --> PARTICLES SIM --> PDG RENDERER --> CONFIG RENDERER --> SIM RENDERER --> PDG DATALOADER --> HDF5 classDef gpu fill:#ff6b6b classDef io fill:#4ecdc4 classDef config fill:#ffe66d class SIM gpu class DATALOADER io class CONFIG config Module Responsibilities ======================= **__main__.py** (CLI & Main Loop) - Entry point and command-line argument parsing - Simulation loop (render each frame, step physics) - Input handling (keyboard, mouse, ImGui) - State management (pause/resume, reset) **config.py** (Constants) - Physics constants (forces, integrators, relativity) - Rendering constants (camera, trails, colors) - Preset definitions - **Size:** ~400 lines **simulation.py** (GPU Physics) - Taichi @ti.kernel GPU kernels - Force computation (Coulomb, gravity, Lorentz, strong) - Particle dynamics (integrate, boundary, collision detection) - Decay & pair production - Trail rendering (Phase 1 optimized) - **Size:** ~2900 lines (largest module) **renderer.py** (Visualization) - Taichi GUI window management - 3D camera and scene setup - Particle rendering (points/spheres + trails) - ImGui control panels - Black hole effects (disk, photon ring, lensing) - Collision flashes and debug overlays **particles.py** (Taichi Field Management) - Initializes Taichi fields (big arrays on GPU) - Position, velocity, type buffers - Trail ring buffers (one per particle) **pdg_table.py** (Particle Catalog) - 40 particles from Particle Data Group - Masses, lifetimes, charges, decay channels - Color mappings for visualization - Quantum numbers (flavor, color, spin) **data_loader.py** (I/O) - HDF5 state import/export - CSV event logging - JSONL physics events Data Flow (Per Frame) ===================== 1. **Input Phase** .. code-block:: text Keyboard/Mouse Events ↓ Renderer.handle_input() ↓ ImGui Sliders → Update force constants in real-time SPACE → Toggle pause +/- → Spawn/remove particles Ctrl+S → Save state to HDF5 2. **Physics Phase** (repeated SUBSTEPS times) .. code-block:: text compute_forces() ↓ Coulomb (pairwise, O(N²)) Gravity (pairwise, O(N²)) Strong force (baryon-baryon, O(N²)) Lorentz E/B (O(N)) Black hole gravity (O(N)) ↓ _integrate_step() ↓ Leapfrog half-kick OR Euler step Velocity clamping (MAX_VELOCITY) Relativity correction (γ) ↓ apply_boundaries() ↓ Reflect or periodic wrapping ↓ detect_collisions() ↓ O(N²) pairwise radius overlap Dispatch: annihilation, decay, elastic scatter Enqueue spawn products ↓ monte_carlo_decay() ↓ Exponential decay law Time dilation (SR + GR) Enqueue spawn products ↓ _apply_spawn_queue() + _finalize_spawn() ↓ Create new particles from collisions/decays ↓ record_trails() ↓ Ring buffer update (latest position to head) ↓ (Leapfrog only) compute_forces() + half-kick 3. **Maintenance Phase** .. code-block:: text do_maintenance() ↓ Fade collision flashes Compact dead particles Update active particle count 4. **Stats Phase** (every 10 frames) .. code-block:: text refresh_stats() ↓ Compute KE, momentum, census Update ImGui display 5. **Render Phase** .. code-block:: text prepare_render() ↓ build_render_data() kernel → GPU buffer build_trail_lines() kernel → GPU vertices (Phase 1 optimized) GPU → CPU (vertex/color uploads) ↓ Renderer.render() ↓ Draw particles (colored spheres) Draw trails (line segments with fade) Draw black hole effects Draw collision flashes Draw starfield Draw ImGui panels (CPU-side) ↓ taichi.ui.show() ↓ Display to screen Taichi GPU Kernels ================== **@ti.kernel decorator:** JIT-compiled GPU code **compute_forces()** [O(N²)] - Nested loop over all particles - Pairwise distance & force calculation - Accumulate force vector per particle - GPU: Each thread processes one particle **_integrate_step()** [O(N)] - Loop over all particles - Update velocity (force/mass acceleration) - Update position (velocity × dt) - Clamp to MAX_VELOCITY - Apply SR gamma correction **detect_collisions()** [O(N²)] - Nested loop over particle pairs - Sphere-sphere overlap test - If collision → spawn products (enqueued) - Dispatch collision type (annihilation, decay, scatter) **monte_carlo_decay()** [O(N)] - Loop over particles - Compute decay probability (exponential law) - If decay occurs → randomly select channel, spawn products **build_trail_lines()** [O(N)] - Per-particle: extract ring buffer positions - **Phase 1 skip logic:** - Skip if type == PHOTON - Skip if frozen - Skip if speed < MIN_TRAIL_SPEED_FOR_RENDER - Skip if trail too short - Generate line segment vertices for rendering - Write to GPU arrays (trail_vertices, trail_colors) All kernels are memory-coalesced (sequential GPU thread access to arrays). State Management ================ **Active Particle List:** - ``num_active[None]`` — Count of live particles (compacted list) - Particles are indexed [0, num_active) - Dead particles moved to end, count decremented - Avoids fragmentation **Ring Buffer Trails:** - Per-particle: ``trail_pos[i, :]`` = ring buffer of 40/20/5 positions - ``trail_head[i]`` = index of newest position (wraps 0 to TRAIL_LENGTH-1) - ``trail_count[i]`` = number of valid positions (starts at 1, fills up to TRAIL_LENGTH) **Spawn Queue:** - Temporary list of particles to create (from collisions/decays) - Processed end-of-step (after collision detection) - New particles inserted into main structure **Force State:** - Current force constants (read from config each frame) - Derived fields (magnetic field magnitude, velocity-dependent damping) Integrators =========== **Euler (first-order):** .. math:: \vec{v}_{n+1} &= \vec{v}_n + \frac{\vec{F}}{m} \cdot dt \\ \vec{r}_{n+1} &= \vec{r}_n + \vec{v}_{n+1} \cdot dt - Simple, fast - Poor energy conservation - Energy drifts monotonically **Leapfrog (symplectic, second-order):** .. math:: \vec{v}_{n+1/2} &= \vec{v}_n + \frac{\vec{F}}{m} \cdot \frac{dt}{2} \\ \vec{r}_{n+1} &= \vec{r}_n + \vec{v}_{n+1/2} \cdot dt \\ \vec{v}_{n+1} &= \vec{v}_{n+1/2} + \frac{\vec{F}}{m} \cdot \frac{dt}{2} - Symplectic → preserves phase-space volume - Better energy conservation - Slightly higher computational cost - **Recommended** for long simulations Particle Lifecycle ================== 1. **Spawn** — Random position/velocity or from decay/collision 2. **Alive** — Part of main particle pool, physics applied 3. **Collision** → New particles spawned (or annihilation) 4. **Decay** → New particles spawned, original removed 5. **Dead** — Removed from active pool (compacted) Removal causes: - Natural decay (exponential lifetime) - Boundary escape (if BOUNDARY_MODE="none") - Annihilation in collision GPU Memory Layout ================= Taichi allocates large fixed arrays on GPU at startup: .. list-table:: :header-rows: 1 * - Field - Size - Purpose * - ``pos[MAX_PARTICLES]`` - 100 particles × 3 floats = 1.2 KB - Current positions * - ``vel[MAX_PARTICLES]`` - 100 particles × 3 floats = 1.2 KB - Current velocities * - ``ptype[MAX_PARTICLES]`` - 100 particles × 1 int = 400 B - Particle type (PDG ID) * - ``trail_pos[MAX_PARTICLES, TRAIL_LENGTH]`` - 100 × 40 × 3 floats = 48 KB - Trail history (ring buffer) * - ``trail_vertices[MAX_PARTICLES * TRAIL_LENGTH * 2]`` - 100 × 40 × 2 × 3 floats = 96 KB - Trail geometry (GPU-side only) * - ``trail_colors[MAX_PARTICLES * TRAIL_LENGTH * 2]`` - 100 × 40 × 2 × 3 floats = 96 KB - Trail colors (GPU-side only) **Total:** ~250 KB (negligible on modern GPUs) Phase 1 optimization reduces trail vertex buffer by 10x (same field size, fewer rendered vertices). Design Patterns =============== **Kernel + Taichi Fields:** - All heavy computation in @ti.kernel functions - Fields auto-parallelized across GPU threads - No explicit parallelism in Python code **Configuration as Constants:** - All tunable parameters in config.py - Imported at module load time - Changes require restart **Event-Driven Physics:** - Collisions and decays detected at step - Products enqueued, applied end-of-step - Prevents iterator invalidation in nested loops **Compacted Active List:** - Dead particles not physically removed - Active count decremented, dead moved to end - Prevents fragmentation and repeated allocation Error Handling ============== **Physics assertions (disabled in release):** - Energy bounds checking - NaN/Inf detection in forces - Particle count sanity checks **Boundary checks:** - Velocity clamping (prevent overflow) - Force magnitude clamping in collider - Array index bounds (in Taichi kernel) **Graceful degradation:** - If GPU out of memory → fallback to CPU (if Taichi supports) - If particle spawn fails → log warning, continue - If export fails → user gets error box, sim continues Performance Considerations ========================== **GPU Memory Coalescing:** - Kernel loops iterate particles sequentially - Threads access same field sequentially - GPU caches efficiently (coalesced memory access) **Reduced Branching:** - Hardcoded trail skip conditions (photons, frozen) for perf - No runtime config branches in inner loops **Phase 1 Optimization:** - 10x vertex reduction (400 → 40 segments) - Skip logic pre-filters particles before rendering - Result: 2-3x FPS improvement at 1k particles See :ref:`Performance Tuning ` for more optimization details.