Python API Reference

Public Python API for scripting and extending the simulation.

Simulation Class

Main simulation interface:

from quantum_collider_sandbox.simulation import Simulation

sim = Simulation(preset="default", particles=20)

Constructor Parameters:

  • preset (str) — Name of preset (“default”, “lhc_pp”, “black_hole”, etc.)

  • particles (int) — Initial particle count (0-100)

  • config_override (dict) — Override config parameters as key-value pairs

Methods:

# Main simulation loop
sim.step()  # Advance by one timestep
sim.reset()  # Reinitialize with current preset

# Particle management
sim.add_particle(x, y, z, vx, vy, vz, particle_type="electron")
sim.remove_particle(index)
sim.get_particle_count()
sim.get_particle_positions()  # Returns Nx3 numpy array
sim.get_particle_velocities()  # Returns Nx3 numpy array
sim.get_particle_types()  # Returns N integers (PDG IDs)

# Physics queries
sim.compute_kinetic_energy()  # Total KE (float)
sim.compute_momentum()  # Total momentum magnitude (float)
sim.get_forces()  # Current force vector per particle (Nx3)

# State I/O
sim.save_state(filename)  # Export to HDF5
sim.load_state(filename)  # Import from HDF5
sim.export_events(filename)  # Save physics log to JSONL

Example:

from quantum_collider_sandbox.simulation import Simulation
import numpy as np

# Create sim
sim = Simulation(preset="lhc_pp", particles=40)

# Run 100 steps
energies = []
for step in range(100):
    sim.step()
    energies.append(sim.compute_kinetic_energy())

# Plot results
import matplotlib.pyplot as plt
plt.plot(energies)
plt.ylabel("Kinetic Energy (au)")
plt.xlabel("Timestep")
plt.show()

Configuration Module

Access and override configuration at runtime:

from quantum_collider_sandbox import config

# Read current values
print(config.COULOMB_K)  # 40.0
print(config.GRAVITY_G)  # 6.0
print(config.TRAIL_LENGTH)  # 40

# Override before creating Simulation
config.TRAIL_LENGTH = 20
config.COULOMB_K = 80.0
sim = Simulation(preset="default")  # Uses overridden values

Key configuration constants:

  • Physics: DT, SUBSTEPS, INTEGRATOR, USE_RELATIVITY

  • Forces: COULOMB_K, GRAVITY_G, STRONG_FORCE_K, MAGNETIC_FIELD, E_FIELD

  • Rendering: WINDOW_WIDTH, WINDOW_HEIGHT, CAMERA_POS, BASE_PARTICLE_RADIUS

  • Trails (Phase 1): TRAIL_LENGTH, MIN_TRAIL_SPEED_FOR_RENDER, MIN_TRAIL_LENGTH_FOR_RENDER

  • Presets: PRESETS dict

Particle Data Group (PDG) Table

Access particle properties:

from quantum_collider_sandbox.pdg_table import (
    PARTICLES,
    PDG_PARTICLES,
    get_particle_mass,
    get_particle_lifetime,
    get_particle_color,
)

# Get particle by name
electron = PARTICLES["electron"]
print(electron["mass"])  # Rest mass (MeV/c²)
print(electron["lifetime"])  # Proper lifetime (seconds)
print(electron["charge"])  # Electric charge (units of e)
print(electron["decay_channels"])  # List of decay modes

# Get by PDG ID
pdg = 11  # electron
props = PDG_PARTICLES.get(pdg)

# Global functions
mass = get_particle_mass("muon")
lifetime = get_particle_lifetime("pion")
color = get_particle_color("electron")  # RGB tuple

Data Loader Module

Import/export simulation state and events:

from quantum_collider_sandbox.data_loader import DataLoader

loader = DataLoader()

# Export state to HDF5
loader.export_state(
    particles=sim.get_particles(),
    filename="state.h5"
)

# Import state from HDF5
particles = loader.import_state("state.h5")
sim.load_particles(particles)

# Log physics events
loader.log_collision(
    particle_a_type=11,  # electron (PDG ID)
    particle_b_type=-11,  # positron
    products=[22, 22],  # two photons
    position=(0, 0, 0),
    timestamp=0.001
)

Renderer Interface

Advanced rendering control (for extensions):

from quantum_collider_sandbox.renderer import Renderer
from quantum_collider_sandbox.simulation import Simulation

sim = Simulation(preset="default")
renderer = Renderer(sim)

# Main render loop
while not renderer.should_close():
    renderer.handle_input()
    sim.step()
    renderer.render()

renderer.close()

Methods:

  • handle_input() — Process keyboard/mouse

  • render() — Draw current frame

  • update_camera(pos, lookat, fov) — Change view

  • set_overlay_text(text) — Display debug info

  • should_close() — Check window close button

Example: Custom Simulation Loop

import numpy as np
from quantum_collider_sandbox.simulation import Simulation
from quantum_collider_sandbox import config

# Custom configuration
config.COULOMB_K = 100.0
config.GRAVITY_G = 0.0  # Disable gravity
config.TRAIL_LENGTH = 20  # Phase 1 optimization
config.MIN_TRAIL_SPEED_FOR_RENDER = 0.1

# Create and run
sim = Simulation(preset="playground", particles=50)

results = {
    "time": [],
    "energy": [],
    "momentum": [],
    "particle_count": [],
}

for t in range(1000):
    sim.step()

    if t % 10 == 0:
        results["time"].append(t * config.DT)
        results["energy"].append(sim.compute_kinetic_energy())
        results["momentum"].append(sim.compute_momentum())
        results["particle_count"].append(sim.get_particle_count())

# Save and analyze
sim.export_events("events.jsonl")
sim.save_state("final_state.h5")

# Plot
import matplotlib.pyplot as plt
plt.figure(figsize=(12, 4))

plt.subplot(1, 3, 1)
plt.plot(results["time"], results["energy"])
plt.ylabel("Energy (au)")
plt.xlabel("Time (sim units)")

plt.subplot(1, 3, 2)
plt.plot(results["time"], results["momentum"])
plt.ylabel("Momentum (au)")

plt.subplot(1, 3, 3)
plt.plot(results["time"], results["particle_count"])
plt.ylabel("N particles")

plt.tight_layout()
plt.savefig("results.png", dpi=150)
print("Saved results.png")

Example: Batch Parameter Sweep

from quantum_collider_sandbox.simulation import Simulation
from quantum_collider_sandbox import config
import json

# Sweep Coulomb strength
results = []

for k in [10, 20, 40, 80, 160]:
    config.COULOMB_K = k
    sim = Simulation(preset="rutherford", particles=30)

    energies = []
    for step in range(500):
        sim.step()
        if step % 10 == 0:
            energies.append(sim.compute_kinetic_energy())

    results.append({
        "coulomb_k": k,
        "avg_energy": np.mean(energies),
        "final_particles": sim.get_particle_count(),
    })

# Save and display
with open("coulomb_sweep.json", "w") as f:
    json.dump(results, f, indent=2)

for r in results:
    print(f"K={r['coulomb_k']}: Energy={r['avg_energy']:.2f}, "
          f"Particles={r['final_particles']}")

Extending the Simulation

Add custom force:

  1. Edit simulation.py

  2. Add kernel to compute_forces()

  3. Update config with new constant

Add custom preset:

  1. Edit config.py

  2. Add entry to PRESETS dict:

    PRESETS["my_preset"] = {
        "preset_name": "My Custom Experiment",
        "initial_particles": 25,
        "coulomb_k": 50.0,
        # ... other params
    }
    
  3. Reference in UI dropdown

Add new particle type:

  1. Edit pdg_table.py

  2. Add entry to PARTICLES dict with mass, lifetime, decay channels

  3. Assign unique PDG ID and color

See Development guide for more extending details.

Thread Safety Notes

NOT thread-safe:

  • Taichi kernels automatically parallelize on GPU (internal)

  • Python API is single-threaded

  • Don’t call sim.step() from multiple threads

Safe patterns:

  • Single main loop calling sim.step() sequentially

  • Multiple simulations in separate processes (not threads)

  • Parallel parameter sweeps via separate Python processes

Performance Tips

Efficient data access:

  • Call get_particle_positions() once per frame, not per particle

  • Batch multiple steps before exporting state

Minimize config changes:

  • Set config before creating Simulation

  • Changing config at runtime has minimal cost but is not recommended

For GPU operations:

  • Taichi kernels are JIT-compiled on first call (slow)

  • Subsequent calls are fast

  • Use reasonable timestep (DT) to avoid numerical issues

Debugging

Enable internal logging:

export TAICHI_LOG_LEVEL=debug
python -m quantum_collider_sandbox

Print simulation state:

print(f"Particles: {sim.get_particle_count()}")
print(f"Energy: {sim.compute_kinetic_energy():.4f}")
print(f"Momentum: {sim.compute_momentum():.4f}")

# First 10 particles
pos = sim.get_particle_positions()
print(pos[:10])