├── .gitignore ├── requirements.txt ├── .coverage ├── bootstrap_demo.py ├── README.md ├── backend_demo.py ├── demo.py ├── ct_tests.py └── ct_framework.py /.gitignore: -------------------------------------------------------------------------------- 1 | fix.py 2 | __pycache__/ -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib 2 | -------------------------------------------------------------------------------- /.coverage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gvelesandro/constructor-theory-simulator/HEAD/.coverage -------------------------------------------------------------------------------- /bootstrap_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | bootstrap_demo.py 4 | 5 | A “universal constructor” that at runtime assembles 6 | both photon‐ and graviton‐tasks into one mega‐constructor, 7 | and then uses it to process different substrates: 8 | • mass → graviton emission/absorption 9 | • charge_site → photon emission/absorption 10 | """ 11 | 12 | import time 13 | from ct_framework import ( 14 | UniversalConstructor, 15 | PhotonEmissionTask, 16 | PhotonAbsorptionTask, 17 | GravitonEmissionTask, 18 | GravitonAbsorptionTask, 19 | Substrate, 20 | Attribute, 21 | ascii_branch, 22 | ) 23 | 24 | 25 | def main(): 26 | # 1) Pick our physical “program”: EM + QG tasks 27 | ELEC = Attribute("charge_site") 28 | MASS = Attribute("mass") 29 | ΔE_ph = 5.0 # photon energy 30 | ΔE_gr = 3.0 # graviton energy 31 | 32 | program = [ 33 | PhotonEmissionTask(ELEC, emission_energy=ΔE_ph, carry_residual=True), 34 | PhotonAbsorptionTask(ELEC, absorption_energy=ΔE_ph), 35 | GravitonEmissionTask(MASS, emission_energy=ΔE_gr), 36 | GravitonAbsorptionTask(MASS, absorption_energy=ΔE_gr), 37 | ] 38 | 39 | # 2) Bootstrap it! 40 | uc = UniversalConstructor() 41 | mega = uc.build(program) 42 | 43 | # 3) Show it in action on a “mass” substrate: 44 | print("=== Graviton demo via universal constructor ===") 45 | m0 = Substrate("m0", MASS, energy=10.0) 46 | branches = mega.perform(m0) 47 | print("Branches after emission:") 48 | for w in branches: 49 | print(" ", w) # calls w.__repr__(), includes energy, clock, etc. 50 | # pick out the graviton branch, then absorb 51 | graviton = next(w for w in branches if w.attr.label == "graviton") 52 | restored = mega.perform(graviton)[0] 53 | print("After absorption: ", restored) 54 | print() 55 | 56 | time.sleep(0.5) 57 | 58 | # 4) And now electromagnetism on a “charge_site”: 59 | print("=== Photon demo via EMConstructor (pure EM) ===") 60 | e0 = Substrate("e0", ELEC, energy=20.0) 61 | from ct_framework import EMConstructor 62 | em_cons = EMConstructor(ELEC, ΔE_ph) 63 | branches = em_cons.perform(e0) 64 | print("Branches after emission:") 65 | for w in branches: 66 | print(" ", w) 67 | photon = next(w for w in branches if w.attr.label == "photon") 68 | restored = em_cons.perform(photon)[0] 69 | print("After absorption: ", restored) 70 | print() 71 | time.sleep(0.5) 72 | 73 | # 5) Finally, show that you can interleave them at will: 74 | print("=== Mixed sequence: emit photon, then graviton ===") 75 | # start with mass, emit graviton 76 | m1 = Substrate("m1", MASS, energy=8.0) 77 | gm_br = mega.perform(m1) 78 | # take the non-graviton branch (the residual-mass branch) 79 | m_res = next(w for w in gm_br if w.attr == MASS) 80 | # now treat that mass as an EM charge site as well: 81 | m_res.attr = ELEC 82 | print("Now EM‐branch on same residual:") 83 | em_br = mega.perform(m_res) 84 | for w in em_br: 85 | print(" ", w) 86 | print("\nDone.") 87 | 88 | 89 | if __name__ == "__main__": 90 | main() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # constructor-theory-simulator 2 | 3 | A **Python implementation** of David Deutsch’s Constructor Theory framework, exposing key concepts—from simple Tasks and branching substrates to quantum-gravity and electromagnetism—entirely in code. Includes a “universal constructor” that can bootstrap itself from a list of Tasks, demonstrating self-replication and the power of Constructor Theory. 4 | 5 | > *“A demonstration of how constructor theory **could** be explored in code, not a high-precision physics engine. For the formal definitions, see David Deutsch and Chiara Marletto’s recent paper “[Constructor Theory of Time](https://arxiv.org/abs/2505.08692)” (May 13, 2025).* 6 | 7 | --- 8 | 9 | ## 🚀 Features 10 | 11 | * **Core framework**: Attributes, Substrates, Tasks, Constructors 12 | * **Irreversible & quantum tasks**: Many-worlds branching, decoherence guards 13 | * **Timers & Clocks**: Simulate proper-time, special/general relativity corrections 14 | * **Fungibility & SwapConstructor**: Free exchange of identical substrates 15 | * **ASCII visualizer**: `ascii_branch()` for quick text-based branch inspection 16 | * **Continuous dynamics**: 1D & 2D substrates, `DynamicsTask`, RK4 & symplectic integrators 17 | * **Coupling tasks**: 18 | 19 | * Gravitational two-body (1D) 20 | * Coulomb coupling (1D) 21 | * Lorentz-force (2D) for charged particles in a magnetic field 22 | * **Quantum-Gravity & Electromagnetism**: Graviton & Photon emission/absorption Tasks 23 | * **UniversalConstructor**: Bootstraps any list of Tasks into a working Constructor 24 | * **Pluggable Backend Architecture**: Modular task ontologies for different physics domains 25 | * **Hydrogen atom constructors**: Excitation, deexcitation and two-atom collisions 26 | * **Demo scripts**: 27 | 28 | * `demo.py` – shows every constructor in action 29 | * `bootstrap_demo.py` – elegant self-replication via the UniversalConstructor 30 | * `backend_demo.py` – demonstrates the pluggable backend system 31 | 32 | --- 33 | 34 | ## 🛠 Getting Started 35 | 36 | ### Prerequisites 37 | 38 | * Python 3.8+ 39 | * (Optional) `matplotlib` for phase-space plots 40 | 41 | ### Installation 42 | 43 | ```bash 44 | git clone https://github.com/gvelesandro/constructor-theory-simulator.git 45 | cd constructor-theory-simulator 46 | ``` 47 | 48 | ### Run Unit Tests 49 | 50 | ```bash 51 | python -m unittest ct_tests.py 52 | ``` 53 | 54 | ### Run Demos 55 | 56 | ```bash 57 | python demo.py 58 | python bootstrap_demo.py 59 | python backend_demo.py 60 | ``` 61 | 62 | > **Note:** If you don’t have `matplotlib`, the demos will still run; plots will simply be skipped with a warning. 63 | 64 | --- 65 | 66 | ## 🎯 Usage Example 67 | 68 | ```python 69 | from ct_framework import ( 70 | Attribute, Substrate, 71 | PhotonEmissionTask, PhotonAbsorptionTask, 72 | UniversalConstructor, ascii_branch 73 | ) 74 | 75 | # 1) Define your “program” of photon Tasks 76 | ELEC = Attribute("charge_site") 77 | prog = [ 78 | PhotonEmissionTask(ELEC, emission_energy=5.0, carry_residual=False), 79 | PhotonAbsorptionTask(ELEC, absorption_energy=5.0) 80 | ] 81 | 82 | # 2) Build a Constructor at runtime 83 | uc = UniversalConstructor() 84 | em_cons = uc.build(prog) 85 | 86 | # 3) Emit a photon 87 | atom = Substrate("A", ELEC, energy=20.0) 88 | branches = em_cons.perform(atom) 89 | print(ascii_branch(branches)) 90 | # => * charge_site (A) 91 | # * photon (A) 92 | 93 | # 4) Absorb it back 94 | photon = next(w for w in branches if w.attr.label=="photon") 95 | restored = em_cons.perform(photon)[0] 96 | print(restored) 97 | # => A:charge_site(E=20.0,Q=0,t=2,F=charge_site) 98 | ``` 99 | 100 | ## 🔌 Pluggable Backend System 101 | 102 | The framework now supports a modular backend architecture for different task ontologies: 103 | 104 | ```python 105 | from ct_framework import ( 106 | UniversalConstructor, Substrate, Attribute, 107 | ElectromagnetismBackend, QuantumGravityBackend 108 | ) 109 | 110 | # 1) Use individual physics backends 111 | uc = UniversalConstructor() 112 | 113 | # Pure electromagnetism 114 | em_constructor = uc.build_from_backends(["electromagnetism"]) 115 | 116 | # Pure quantum gravity 117 | qg_constructor = uc.build_from_backends(["quantum_gravity"]) 118 | 119 | # 2) Combine multiple backends 120 | unified_constructor = uc.build_from_backends([ 121 | "electromagnetism", 122 | "quantum_gravity", 123 | "hydrogen_atoms" 124 | ]) 125 | 126 | # 3) Create custom backends 127 | class CustomPhysicsBackend: 128 | def get_tasks(self): 129 | return [/* your custom tasks */] 130 | 131 | def get_name(self): 132 | return "custom_physics" 133 | 134 | # 4) Available built-in backends: 135 | # - "electromagnetism": photon emission/absorption 136 | # - "quantum_gravity": graviton emission/absorption 137 | # - "hydrogen_atoms": H excitation/deexcitation 138 | # - "continuous_dynamics": integrator tasks 139 | ``` 140 | 141 | Run `python backend_demo.py` to see the full backend system in action! 142 | 143 | --- 144 | 145 | ## 🤝 Contributing 146 | 147 | This is intended as an **educational resource** and proof-of-concept. Contributions are very welcome! Please: 148 | 149 | * File issues for missing tasks or physics modules 150 | * Submit pull requests for new constructors (e.g. chemical reactions, friction) 151 | * Improve documentation or add more demos 152 | 153 | --- 154 | 155 | ## 📜 License 156 | 157 | Released under the **MIT License**. 158 | 159 | --- 160 | 161 | ## 🙏 Acknowledgments 162 | 163 | * Inspired by David Deutsch and Chiara Marletto’s work in **Constructor Theory** and their May 13, 2025 paper “[Constructor Theory of Time](https://arxiv.org/abs/2505.08692).” 164 | * Thanks to the quantum-foundations community for feedback and discussion. 165 | -------------------------------------------------------------------------------- /backend_demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | backend_demo.py 4 | 5 | Demonstrates the pluggable backend architecture for task ontologies. 6 | Shows how different physics domains can be loaded separately or combined. 7 | """ 8 | 9 | import time 10 | from ct_framework import ( 11 | # Backend system 12 | UniversalConstructor, 13 | BackendRegistry, 14 | ElectromagnetismBackend, 15 | QuantumGravityBackend, 16 | HydrogenBackend, 17 | ContinuousDynamicsBackend, 18 | get_global_registry, 19 | 20 | # Core types 21 | Substrate, 22 | Attribute, 23 | ascii_branch, 24 | ) 25 | 26 | 27 | def demo_individual_backends(): 28 | """Demonstrate using individual physics backends.""" 29 | print("=== Individual Backend Demonstrations ===") 30 | 31 | uc = UniversalConstructor() 32 | 33 | # 1. Electromagnetism only 34 | print("\n--- Electromagnetism Backend ---") 35 | em_constructor = uc.build_from_backends(["electromagnetism"]) 36 | 37 | charge_sub = Substrate("electron", Attribute("charge_site"), energy=20.0) 38 | em_worlds = em_constructor.perform(charge_sub) 39 | print("EM emission results:") 40 | for w in em_worlds: 41 | print(f" {w}") 42 | 43 | # Absorb the photon back 44 | photon = next(w for w in em_worlds if w.attr.label == "photon") 45 | absorbed = em_constructor.perform(photon) 46 | print(f"After absorption: {absorbed[0]}") 47 | 48 | # 2. Quantum Gravity only 49 | print("\n--- Quantum Gravity Backend ---") 50 | qg_constructor = uc.build_from_backends(["quantum_gravity"]) 51 | 52 | mass_sub = Substrate("particle", Attribute("mass"), energy=15.0) 53 | qg_worlds = qg_constructor.perform(mass_sub) 54 | print("QG emission results:") 55 | for w in qg_worlds: 56 | print(f" {w}") 57 | 58 | # 3. Hydrogen atoms only 59 | print("\n--- Hydrogen Atom Backend ---") 60 | h_constructor = uc.build_from_backends(["hydrogen_atoms"]) 61 | 62 | h_ground = Substrate("H1", Attribute("H_ground"), energy=0.0) 63 | h_excited = h_constructor.perform(h_ground)[0] 64 | print(f"Excited hydrogen: {h_excited}") 65 | 66 | h_decay = h_constructor.perform(h_excited) 67 | print("Decay products:") 68 | for w in h_decay: 69 | print(f" {w}") 70 | 71 | 72 | def demo_combined_backends(): 73 | """Demonstrate combining multiple backends.""" 74 | print("\n=== Combined Backend Demonstration ===") 75 | 76 | uc = UniversalConstructor() 77 | 78 | # Create a universal physics constructor 79 | physics_constructor = uc.build_from_backends([ 80 | "electromagnetism", 81 | "quantum_gravity", 82 | "hydrogen_atoms" 83 | ]) 84 | 85 | print("\nUniversal physics constructor can handle:") 86 | 87 | # Test with different substrate types 88 | substrates = [ 89 | Substrate("e-", Attribute("charge_site"), energy=25.0), 90 | Substrate("mass", Attribute("mass"), energy=18.0), 91 | Substrate("H", Attribute("H_ground"), energy=5.0), 92 | ] 93 | 94 | for sub in substrates: 95 | worlds = physics_constructor.perform(sub) 96 | print(f"\n{sub.name} ({sub.attr.label}) → {len(worlds)} outcomes:") 97 | for w in worlds: 98 | print(f" {w}") 99 | 100 | 101 | def demo_custom_backend(): 102 | """Demonstrate creating and using a custom backend.""" 103 | print("\n=== Custom Backend Demonstration ===") 104 | 105 | # Create custom attributes for a fictional particle physics scenario 106 | QUARK = Attribute("quark") 107 | GLUON = Attribute("gluon") 108 | 109 | # Define a custom backend for strong force interactions 110 | class StrongForceBackend: 111 | def get_tasks(self): 112 | from ct_framework import Task 113 | return [ 114 | Task( 115 | "quark_gluon_emission", 116 | QUARK, 117 | [(QUARK, -2.0, 0), (GLUON, 0, 0)], 118 | quantum=True, 119 | irreversible=True, 120 | clock_inc=1 121 | ), 122 | Task( 123 | "gluon_absorption", 124 | GLUON, 125 | [(QUARK, 2.0, 0)], 126 | quantum=True, 127 | clock_inc=1 128 | ) 129 | ] 130 | 131 | def get_name(self): 132 | return "strong_force" 133 | 134 | def get_description(self): 135 | return "Strong force (QCD) interactions with quarks and gluons" 136 | 137 | # Create and register the backend 138 | strong_backend = StrongForceBackend() 139 | registry = BackendRegistry() 140 | registry.register(strong_backend) 141 | 142 | # Build constructor with the custom backend 143 | uc = UniversalConstructor(backend_registry=registry) 144 | strong_constructor = uc.build_with_backend(strong_backend) 145 | 146 | # Test the custom physics 147 | quark = Substrate("up_quark", QUARK, energy=10.0) 148 | worlds = strong_constructor.perform(quark) 149 | 150 | print(f"Custom strong force backend:") 151 | print(f" Backend: {strong_backend.get_name()}") 152 | print(f" Description: {strong_backend.get_description()}") 153 | print(f" Tasks: {len(strong_backend.get_tasks())}") 154 | 155 | print(f"\nQuark interaction results:") 156 | for w in worlds: 157 | print(f" {w}") 158 | 159 | # Test gluon absorption 160 | gluon = next(w for w in worlds if w.attr == GLUON) 161 | absorbed = strong_constructor.perform(gluon) 162 | print(f"After gluon absorption: {absorbed[0]}") 163 | 164 | 165 | def demo_backend_swapping(): 166 | """Demonstrate swapping between different backend configurations.""" 167 | print("\n=== Backend Swapping Demonstration ===") 168 | 169 | uc = UniversalConstructor() 170 | 171 | # Same substrate, different physics backends 172 | test_sub = Substrate("particle", Attribute("charge_site"), energy=20.0) 173 | 174 | scenarios = [ 175 | (["electromagnetism"], "Pure EM"), 176 | (["electromagnetism", "quantum_gravity"], "EM + Quantum Gravity"), 177 | (["electromagnetism", "hydrogen_atoms"], "EM + Hydrogen"), 178 | (["electromagnetism", "quantum_gravity", "hydrogen_atoms"], "Full Physics") 179 | ] 180 | 181 | for backend_names, description in scenarios: 182 | constructor = uc.build_from_backends(backend_names) 183 | worlds = constructor.perform(test_sub.clone()) 184 | 185 | print(f"\n{description}:") 186 | print(f" Backends: {', '.join(backend_names)}") 187 | print(f" Outcomes: {len(worlds)}") 188 | for w in worlds: 189 | print(f" {w}") 190 | 191 | 192 | def main(): 193 | """Run all backend demonstrations.""" 194 | print("🔬 Constructor Theory: Pluggable Backend Architecture Demo") 195 | print("=" * 60) 196 | 197 | # Show available backends 198 | registry = get_global_registry() 199 | print(f"\nAvailable backends: {', '.join(registry.list_backends())}") 200 | 201 | demo_individual_backends() 202 | demo_combined_backends() 203 | demo_custom_backend() 204 | demo_backend_swapping() 205 | 206 | print("\n" + "=" * 60) 207 | print("✅ Backend architecture demonstration complete!") 208 | print("\nKey Benefits:") 209 | print("• Modular physics: Each domain can be used independently") 210 | print("• Composable: Mix and match different physics backends") 211 | print("• Extensible: Easy to add new physics domains") 212 | print("• Testable: Isolate and test specific physics behaviors") 213 | 214 | 215 | if __name__ == "__main__": 216 | main() -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | try: 4 | import matplotlib.pyplot as plt 5 | except ImportError: # pragma: no cover - optional plotting 6 | plt = None 7 | 8 | 9 | from ct_framework import ( 10 | Substrate, 11 | Attribute, 12 | Task, 13 | Constructor, 14 | ActionConstructor, 15 | MultiSubstrateTask, 16 | MultiConstructor, 17 | TimerSubstrate, 18 | TimerConstructor, 19 | ClockConstructor, 20 | SwapConstructor, 21 | ascii_branch, 22 | plot_phase_space, 23 | ContinuousSubstrate, 24 | DynamicsTask, 25 | RK4Task, 26 | SymplecticEulerTask, 27 | grav_coupling_fn, 28 | ContinuousSubstrate2D, 29 | Dynamics2DTask, 30 | RK42DTask, 31 | SymplecticEuler2DTask, 32 | # Electromagnetism imports 33 | GRAVITON, 34 | PHOTON, 35 | PhotonEmissionTask, 36 | PhotonAbsorptionTask, 37 | EMConstructor, 38 | coulomb_coupling_fn, 39 | # Lorentz-force imports 40 | FieldSubstrate, 41 | lorentz_coupling_fn, 42 | # Hydrogen imports 43 | HYDROGEN_GROUND, 44 | HYDROGEN_EXCITED, 45 | HYDROGEN_MOLECULE, 46 | HydrogenAtomConstructor, 47 | HydrogenInteractionConstructor, 48 | ) 49 | 50 | 51 | def demo_task_constructor(): 52 | print("=== Task & Constructor Demo ===") 53 | g, e = Attribute("ground"), Attribute("excited") 54 | decay = Task("decay", e, [(g, 0, 0)]) 55 | cons = Constructor([decay]) 56 | p = Substrate("P", e, energy=1) 57 | print(ascii_branch(cons.perform(p))) 58 | print() 59 | 60 | 61 | def demo_action_constructor(): 62 | print("=== ActionConstructor (Least Action) Demo ===") 63 | inp = Attribute("in") 64 | A1, B1 = Attribute("A"), Attribute("B") 65 | low = Task("low", inp, [(A1, 0, 0)], action_cost=1.0) 66 | high = Task("high", inp, [(B1, 0, 0)], action_cost=2.0) 67 | ac = ActionConstructor([low, high]) 68 | res = ac.perform(Substrate("X", inp, energy=1))[0] 69 | print("Picked branch:", res) 70 | print() 71 | 72 | 73 | def demo_multi_constructor(): 74 | print("=== MultiConstructor (Gravitational Coupling) Demo ===") 75 | cs1 = ContinuousSubstrate( 76 | "m1", 0.0, 0.0, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1 77 | ) 78 | cs2 = ContinuousSubstrate( 79 | "m2", 1.0, 0.0, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1 80 | ) 81 | grav_task = MultiSubstrateTask("grav", [cs1.attr, cs2.attr], grav_coupling_fn) 82 | mc = MultiConstructor([grav_task]) 83 | for s1, s2 in mc.perform([cs1, cs2]): 84 | print(s1, s2) 85 | print() 86 | 87 | 88 | def demo_timer_constructor(): 89 | print("=== TimerConstructor Demo ===") 90 | t = TimerSubstrate("T", 0.5) 91 | T = TimerConstructor() 92 | while t.attr.label != "halted": 93 | T.perform(t) 94 | print("Timer state:", t.attr.label) 95 | time.sleep(0.1) 96 | print() 97 | 98 | 99 | def demo_clock_constructor(): 100 | print("=== ClockConstructor Demo ===") 101 | clk = Substrate("C", Attribute("clock"), energy=1) 102 | ticker = ClockConstructor(0.0) 103 | for _ in range(3): 104 | ticker.perform(clk) 105 | print("Clock value:", clk.clock) 106 | print() 107 | 108 | 109 | def demo_swap_constructor(): 110 | print("=== SwapConstructor Demo ===") 111 | data = Attribute("data") 112 | a = Substrate("A", data, energy=1, fungible_id="X") 113 | b = Substrate("B", data, energy=1, fungible_id="X") 114 | print("Before swap:", a.name, b.name) 115 | SwapConstructor.swap(a, b) 116 | print(" After swap:", a.name, b.name) 117 | print() 118 | 119 | 120 | def demo_phase_space(): 121 | print("=== Phase-Space Visualiser Demo ===") 122 | cs = ContinuousSubstrate( 123 | "traj", 0.0, 1.0, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1 124 | ) 125 | traj = [cs] 126 | for _ in range(5): 127 | cs = DynamicsTask().apply(cs)[0] 128 | traj.append(cs) 129 | plot_phase_space({"simple": traj}) 130 | print() 131 | 132 | 133 | def demo_integrators(): 134 | print("=== Integrators Demo ===") 135 | pot = lambda x: 0.5 * x * x 136 | cs = ContinuousSubstrate("int", 1.0, 0.0, mass=1.0, potential_fn=pot, dt=0.1) 137 | print(" DynamicsTask: ", DynamicsTask().apply(cs.clone())[0]) 138 | print(" RK4Task: ", RK4Task().apply(cs.clone())[0]) 139 | print(" SympEulerTask: ", SymplecticEulerTask().apply(cs.clone())[0]) 140 | print() 141 | 142 | 143 | def demo_two_body_GR(): 144 | print("=== Two-Body GR Demo (1D) ===") 145 | G = 6.67430e-11 146 | dt = 0.1 147 | STEPS = 50 148 | s1 = ContinuousSubstrate( 149 | "Body1", -1.0, 0.5, mass=5.0, potential_fn=lambda x: 0.0, dt=dt 150 | ) 151 | s2 = ContinuousSubstrate( 152 | "Body2", 1.0, -0.3, mass=3.0, potential_fn=lambda x: 0.0, dt=dt 153 | ) 154 | grav_task = MultiSubstrateTask("grav", [s1.attr, s2.attr], grav_coupling_fn) 155 | mc = MultiConstructor([grav_task]) 156 | for i in range(1, STEPS + 1): 157 | s1, s2 = mc.perform([s1, s2])[0] 158 | s1 = DynamicsTask().apply(s1)[0] 159 | s2 = DynamicsTask().apply(s2)[0] 160 | print(f"Final positions: {s1.x:.3f}, {s2.x:.3f}") 161 | print() 162 | 163 | 164 | def demo_orbit_conditions(): 165 | print("=== Circular‐Orbit Conditions Demo ===") 166 | G, m1, m2, r = 1.0, 5.0, 3.0, 1.0 167 | μ = m1 * m2 / (m1 + m2) 168 | v_rel = math.sqrt(G * m1 * m2 / (μ * r)) 169 | print(f"r={r}, μ={μ:.3f}, v_rel={v_rel:.3f}") 170 | print() 171 | 172 | 173 | def demo_2d_orbit(integrator, steps=100): 174 | print(f"=== 2D Orbit with {integrator.__class__.__name__} ({steps} steps) ===") 175 | G, M = 1.0, 1.0 176 | potential2d = lambda x, y: -G * M / math.hypot(x, y) 177 | m_cent, m_sat, r0 = 100.0, 1.0, 1.0 178 | v0 = math.sqrt(G * m_cent / r0) 179 | sat = ContinuousSubstrate2D( 180 | "Sat", r0, 0.0, 0.0, m_sat * v0, mass=m_sat, potential_fn=potential2d, dt=0.01 181 | ) 182 | cent = ContinuousSubstrate2D( 183 | "Cent", 0.0, 0.0, 0.0, 0.0, mass=m_cent, potential_fn=lambda x, y: 0.0, dt=0.01 184 | ) 185 | 186 | def grav2d(subs): 187 | s1, s2 = subs 188 | dx, dy = s2.x - s1.x, s2.y - s1.y 189 | r2 = dx * dx + dy * dy 190 | F = G * s1.mass * s2.mass / (r2 if r2 else 1e-6) 191 | dirx, diry = dx / math.sqrt(r2), dy / math.sqrt(r2) if r2 else (1.0, 0.0) 192 | s1n, s2n = s1.clone(), s2.clone() 193 | s1n.px += F * dirx * s1.dt 194 | s1n.py += F * diry * s1.dt 195 | s2n.px -= F * dirx * s1.dt 196 | s2n.py -= F * diry * s1.dt 197 | return [[s1n, s2n]] 198 | 199 | mc = MultiConstructor([MultiSubstrateTask("grav2d", [sat.attr, cent.attr], grav2d)]) 200 | xs, ys = [], [] 201 | for _ in range(steps): 202 | sat, cent = mc.perform([sat, cent])[0] 203 | sat = integrator.apply(sat)[0] 204 | xs.append(sat.x) 205 | ys.append(sat.y) 206 | if plt is None: 207 | print("matplotlib not available; skipping 2D orbit plot.") 208 | else: 209 | plt.figure(figsize=(5, 5)) 210 | plt.plot(xs, ys, "-", lw=1) 211 | plt.scatter([0], [0], color="red", s=30, label="Center") 212 | plt.axis("equal") 213 | plt.title("2D Orbit") 214 | plt.legend() 215 | plt.show() 216 | print() 217 | 218 | 219 | # Electromagnetism demos 220 | 221 | 222 | def demo_photon_emission_absorption(): 223 | print("=== Photon Emission/Absorption Demo ===") 224 | ELEC = Attribute("charge_site") 225 | EM = EMConstructor(ELEC, ΔE=5.0) 226 | s = Substrate("Atom", ELEC, energy=20.0) 227 | branches = EM.perform(s) 228 | for w in branches: 229 | print(w) 230 | photon = next(w for w in branches if w.attr == PHOTON) 231 | print("Absorbing photon...") 232 | back = EM.perform(photon)[0] 233 | print("After absorption:", back) 234 | print() 235 | 236 | 237 | def demo_coulomb_coupling(): 238 | print("=== Coulomb Coupling Demo (1D) ===") 239 | cs1 = ContinuousSubstrate( 240 | "p1", -1.0, 0.0, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1, charge=+1 241 | ) 242 | cs2 = ContinuousSubstrate( 243 | "p2", +1.0, 0.0, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1, charge=+1 244 | ) 245 | task = MultiSubstrateTask("coulomb", [cs1.attr, cs2.attr], coulomb_coupling_fn) 246 | mc = MultiConstructor([task]) 247 | out1, out2 = mc.perform([cs1, cs2])[0] 248 | print("Before coupling:", cs1.p, cs2.p) 249 | print("After coupling: ", out1.p, out2.p) 250 | print() 251 | 252 | 253 | def demo_lorentz_force(): 254 | print("=== Lorentz‐Force Coupling Demo (2D) ===") 255 | cs = ContinuousSubstrate2D( 256 | "e−", 257 | x=0.0, 258 | y=1.0, 259 | px=2.0, 260 | py=3.0, 261 | mass=1.0, 262 | potential_fn=lambda x, y: 0.0, 263 | dt=0.1, 264 | charge=+1, 265 | ) 266 | B = FieldSubstrate("B", Bz=0.5) 267 | task = MultiSubstrateTask("lorentz", [cs.attr, B.attr], lorentz_coupling_fn) 268 | mc = MultiConstructor([task]) 269 | 270 | before = (cs.px, cs.py) 271 | out_cs, out_B = mc.perform([cs, B])[0] 272 | after = (out_cs.px, out_cs.py) 273 | 274 | print(f" Before momentum: px={before[0]:.3f}, py={before[1]:.3f}") 275 | print(f" After momentum: px={after[0]:.3f}, py={after[1]:.3f}") 276 | print(f" B-field (unchanged): Bz={out_B.Bz}") 277 | print() 278 | 279 | 280 | def demo_hydrogen_atom(): 281 | print("=== Hydrogen Atom Demo ===") 282 | cons = HydrogenAtomConstructor() 283 | h = Substrate("H", HYDROGEN_GROUND, energy=0.0) 284 | excited = cons.perform(h)[0] 285 | print("After excitation:", excited) 286 | for w in cons.perform(excited): 287 | print(" ", w) 288 | print() 289 | 290 | 291 | def demo_hydrogen_collision(): 292 | print("=== Hydrogen Collision Demo ===") 293 | hi = HydrogenInteractionConstructor() 294 | h1 = Substrate("H1", HYDROGEN_GROUND, energy=3.0) 295 | h2 = Substrate("H2", HYDROGEN_GROUND, energy=3.0) 296 | result = hi.perform([h1, h2])[0] 297 | for r in result: 298 | print(" ", r) 299 | low1 = Substrate("hA", HYDROGEN_GROUND, energy=1.0) 300 | low2 = Substrate("hB", HYDROGEN_GROUND, energy=1.0) 301 | print("Low energy outcome:", hi.perform([low1, low2])[0]) 302 | print() 303 | 304 | 305 | if __name__ == "__main__": 306 | demo_task_constructor() 307 | demo_action_constructor() 308 | demo_multi_constructor() 309 | demo_timer_constructor() 310 | demo_clock_constructor() 311 | demo_swap_constructor() 312 | demo_phase_space() 313 | demo_integrators() 314 | demo_two_body_GR() 315 | demo_orbit_conditions() 316 | demo_2d_orbit(Dynamics2DTask(), steps=100) 317 | demo_2d_orbit(RK42DTask(), steps=100) 318 | demo_2d_orbit(SymplecticEuler2DTask(), steps=100) 319 | demo_photon_emission_absorption() 320 | demo_coulomb_coupling() 321 | demo_lorentz_force() 322 | demo_hydrogen_atom() 323 | demo_hydrogen_collision() 324 | -------------------------------------------------------------------------------- /ct_tests.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | import unittest 4 | try: 5 | import matplotlib.pyplot as plt 6 | HAS_PLT = True 7 | except ImportError: # pragma: no cover - optional dependency 8 | plt = None 9 | HAS_PLT = False 10 | import ct_framework as ctf 11 | 12 | A = ctf.Attribute # shorthand 13 | 14 | 15 | class TestCTFramework(unittest.TestCase): 16 | def setUp(self): 17 | # basic attributes 18 | self.g, self.e = A("g"), A("e") 19 | self.alt = A("alt") 20 | self.up, self.down = A("up"), A("down") 21 | self.entangled = A("ent") 22 | 23 | excite = ctf.Task( 24 | "excite", self.g, [(self.e, -1, 0)], duration=0.001, clock_inc=1 25 | ) 26 | decay = ctf.Task( 27 | "decay", 28 | self.e, 29 | [(self.g, +1, 0), (self.alt, +1, 0)], 30 | duration=0.001, 31 | clock_inc=1, 32 | ) 33 | ent = ctf.Task("ent", self.up, [(self.entangled, 0, 0)], quantum=True) 34 | deco = ctf.Task( 35 | "deco", 36 | self.entangled, 37 | [(self.up, 0, 0), (self.down, 0, 0)], 38 | irreversible=True, 39 | ) 40 | 41 | self.cons = ctf.Constructor([excite, decay, ent, deco]) 42 | 43 | # 1. excite 44 | def test_excite(self): 45 | s = ctf.Substrate("e-", self.g, 1) 46 | out = self.cons.perform(s)[0] 47 | self.assertEqual(out.attr, self.e) 48 | 49 | # 2. many-worlds decay 50 | def test_many_worlds_decay(self): 51 | worlds = self.cons.perform(ctf.Substrate("e-", self.e, 2)) 52 | self.assertEqual({w.attr for w in worlds}, {self.g, self.alt}) 53 | 54 | # 3. irreversible guard 55 | def test_irreversible(self): 56 | s = ctf.Substrate("x", self.entangled, 1) 57 | self.cons.perform(s) 58 | self.assertEqual(self.cons.perform(s), []) 59 | 60 | # 4. decoherence branches 61 | def test_decoherence(self): 62 | a = ctf.Substrate("A", self.up, 1) 63 | a.entangled_with = a 64 | first = self.cons.perform(a)[0] 65 | branches = self.cons.perform(first) 66 | self.assertEqual({b.attr for b in branches}, {self.up, self.down}) 67 | 68 | # 5. null constructor identity 69 | def test_null_constructor(self): 70 | n = ctf.NullConstructor() 71 | s = ctf.Substrate("idle", self.g, 0) 72 | self.assertIs(n.perform(s)[0], s) 73 | 74 | # 6. timer synchrony 75 | def test_timer(self): 76 | t1 = ctf.TimerSubstrate("T1", 0.03) 77 | t2 = ctf.TimerSubstrate("T2", 0.03) 78 | T = ctf.TimerConstructor() 79 | while t1.attr.label != "halted" or t2.attr.label != "halted": 80 | T.perform(t1) 81 | T.perform(t2) 82 | time.sleep(0.005) 83 | self.assertEqual(t1.attr, t2.attr) 84 | 85 | # 7. composition principle 86 | def test_composition(self): 87 | s = ctf.Substrate("p", self.g, 2) 88 | mid = self.cons.perform(s)[0] 89 | outs = self.cons.perform(mid) 90 | self.assertTrue(outs) 91 | 92 | # 8. charge conservation 93 | def test_charge_conservation(self): 94 | bad = ctf.Task("bad", self.g, [(self.e, -1, +1)]) 95 | self.assertFalse(bad.possible(ctf.Substrate("ion", self.g, 1, charge=0))) 96 | 97 | # 9. clock increment 98 | def test_clock_increment(self): 99 | out = self.cons.perform(ctf.Substrate("clk", self.g, 1))[0] 100 | self.assertEqual(out.clock, 1) 101 | 102 | # 10. special relativity 103 | def test_special_relativity(self): 104 | c, β = 299_792_458, 0.6 105 | γ = 1 / math.sqrt(1 - β**2) 106 | fast = ctf.Substrate("μ", self.g, 1, velocity=β * c) 107 | self.assertAlmostEqual(fast.adjusted_duration(0.02), 0.02 / γ, places=7) 108 | 109 | # 11. general relativity 110 | def test_gravity_redshift(self): 111 | c, g = 299_792_458, 9.8 112 | deep = ctf.Substrate("clock", self.g, 1, grav=g) 113 | self.assertAlmostEqual( 114 | deep.adjusted_duration(0.02), 0.02 * (1 + g / c**2), places=10 115 | ) 116 | 117 | # 12. fungibility 118 | def test_fungibility(self): 119 | a = ctf.Substrate("a", self.g, 1, fungible_id="q") 120 | b = ctf.Substrate("b", self.g, 1, fungible_id="q") 121 | self.assertTrue(a.is_fungible_with(b)) 122 | 123 | # 13. swap fungibles 124 | def test_swap(self): 125 | a = ctf.Substrate("a", self.g, 1, fungible_id="q") 126 | b = ctf.Substrate("b", self.g, 1, fungible_id="q") 127 | ctf.SwapConstructor.swap(a, b) 128 | self.assertEqual({a.name, b.name}, {"a", "b"}) 129 | 130 | # 14. ascii branch 131 | def test_ascii_branch(self): 132 | art = ctf.ascii_branch(self.cons.perform(ctf.Substrate("y", self.e, 2))) 133 | self.assertIn("* g", art) 134 | 135 | # 15. clock constructor tick 136 | def test_clock_constructor(self): 137 | clk = ctf.Substrate("tick", A("clock"), 1) 138 | ticker = ctf.ClockConstructor(0.001) 139 | ticker.perform(clk) 140 | self.assertEqual(clk.clock, 1) 141 | 142 | # 16. Mach–Zehnder interference 143 | def test_mach_zehnder(self): 144 | bs = ctf.Task("BS", A("src"), [(A("p1"), 0, 0), (A("p2"), 0, 0)], quantum=True) 145 | rc = ctf.Task("RC", A("p1"), [(A("int"), 0, 0)], quantum=True) 146 | mz = ctf.Constructor([bs, rc]) 147 | ph = ctf.Substrate("γ", A("src"), 1) 148 | paths = mz.perform(ph) 149 | out = mz.perform(paths[0])[0] 150 | self.assertEqual(out.attr.label, "int") 151 | 152 | # 17. irreversible guard on output 153 | def test_irrev_on_output(self): 154 | s = ctf.Substrate("z", self.entangled, 1) 155 | branch = self.cons.perform(s)[0] 156 | self.assertEqual(self.cons.perform(branch), []) 157 | 158 | # 18. null identity 159 | def test_null_identity(self): 160 | s = ctf.Substrate("id", self.g, 0) 161 | self.assertEqual(ctf.NullConstructor().perform(s)[0].attr, self.g) 162 | 163 | # 19. duration identity 164 | def test_duration_identity(self): 165 | s = ctf.Substrate("rest", self.g, 0) 166 | self.assertEqual(s.adjusted_duration(0.01), 0.01) 167 | 168 | # 20. ActionConstructor single 169 | def test_action_single(self): 170 | inp, out = A("in"), A("out") 171 | t = ctf.Task("only", inp, [(out, 0, 0)], action_cost=0.5) 172 | ac = ctf.ActionConstructor([t]) 173 | res = ac.perform(ctf.Substrate("X", inp, 1)) 174 | self.assertEqual(res[0].attr, out) 175 | 176 | # 21. least-action selection 177 | def test_least_action(self): 178 | inp = A("in") 179 | low = ctf.Task("low", inp, [(A("A"), 0, 0)], action_cost=1.0) 180 | high = ctf.Task("high", inp, [(A("B"), 0, 0)], action_cost=2.0) 181 | ac = ctf.ActionConstructor([low, high]) 182 | res = ac.perform(ctf.Substrate("X", inp, 1)) 183 | self.assertEqual(res[0].attr.label, "A") 184 | 185 | # 22. gravitational coupling 1D 186 | def test_grav_coupling_1d(self): 187 | cs1 = ctf.ContinuousSubstrate( 188 | "m1", 0.0, 0.0, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1 189 | ) 190 | cs2 = ctf.ContinuousSubstrate( 191 | "m2", 1.0, 0.0, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1 192 | ) 193 | task = ctf.MultiSubstrateTask( 194 | "grav", [cs1.attr, cs2.attr], ctf.grav_coupling_fn 195 | ) 196 | mc = ctf.MultiConstructor([task]) 197 | m1n, m2n = mc.perform([cs1, cs2])[0] 198 | self.assertNotEqual(m1n.p, cs1.p) 199 | self.assertNotEqual(m2n.p, cs2.p) 200 | 201 | # 23. phase-space visualiser 202 | @unittest.skipUnless(HAS_PLT, "matplotlib not installed") 203 | def test_phase_space(self): 204 | orig = plt.show 205 | plt.show = lambda *a, **k: None 206 | cs = ctf.ContinuousSubstrate( 207 | "t", 0.0, 1.0, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1 208 | ) 209 | cs2 = cs.clone() 210 | cs2.x += cs2.p / cs2.mass * cs2.dt 211 | cs2.clock += 1 212 | ctf.plot_phase_space({"traj": [cs, cs2]}) 213 | plt.show = orig 214 | 215 | # 24. DynamicsTask (1D) 216 | def test_dynamics_task_1d(self): 217 | pot = lambda x: 0.5 * x * x 218 | cs = ctf.ContinuousSubstrate("cs", 1.0, 0.0, mass=1.0, potential_fn=pot, dt=0.1) 219 | w = ctf.DynamicsTask().apply(cs)[0] 220 | self.assertAlmostEqual(w.p, -0.1, places=5) 221 | self.assertAlmostEqual(w.x, 0.99, places=5) 222 | 223 | # 25. RK4Task (1D) 224 | def test_rk4_task_1d(self): 225 | pot = lambda x: 0.5 * x * x 226 | cs = ctf.ContinuousSubstrate("rk", 1.0, 0.0, mass=1.0, potential_fn=pot, dt=0.1) 227 | w = ctf.RK4Task().apply(cs)[0] 228 | self.assertAlmostEqual(w.p, -math.sin(cs.dt), places=5) 229 | self.assertAlmostEqual(w.x, math.cos(cs.dt), places=5) 230 | 231 | # 26. SymplecticEulerTask (1D) 232 | def test_symp_euler_task_1d(self): 233 | pot = lambda x: 0.5 * x * x 234 | cs = ctf.ContinuousSubstrate("se", 1.0, 0.0, mass=1.0, potential_fn=pot, dt=0.1) 235 | w = ctf.SymplecticEulerTask().apply(cs)[0] 236 | self.assertAlmostEqual(w.p, -0.1, places=5) 237 | self.assertAlmostEqual(w.x, 0.99, places=5) 238 | 239 | # 27. ContinuousSubstrate2D.clone 240 | def test_continuous_substrate2d_clone(self): 241 | fn = lambda x, y: x * y 242 | cs2 = ctf.ContinuousSubstrate2D( 243 | "2d", x=1.0, y=2.0, px=3.0, py=4.0, mass=5.0, potential_fn=fn, dt=0.1 244 | ) 245 | clone = cs2.clone() 246 | self.assertEqual(clone.x, cs2.x) 247 | self.assertEqual(clone.y, cs2.y) 248 | self.assertEqual(clone.px, cs2.px) 249 | self.assertEqual(clone.py, cs2.py) 250 | self.assertIs(clone.potential2d, fn) 251 | 252 | # 28. Dynamics2DTask 253 | def test_dynamics2d_task(self): 254 | fn = lambda x, y: x + y 255 | cs2 = ctf.ContinuousSubstrate2D( 256 | "d2", x=1.0, y=2.0, px=0.0, py=0.0, mass=1.0, potential_fn=fn, dt=1.0 257 | ) 258 | w = ctf.Dynamics2DTask().apply(cs2)[0] 259 | self.assertAlmostEqual(w.px, -1.0, places=5) 260 | self.assertAlmostEqual(w.py, -1.0, places=5) 261 | self.assertAlmostEqual(w.x, 0.0, places=5) 262 | self.assertAlmostEqual(w.y, 1.0, places=5) 263 | 264 | # 29. RK42DTask constant potential 265 | def test_rk42d_task_constant(self): 266 | fn = lambda x, y: 0.0 267 | cs2 = ctf.ContinuousSubstrate2D( 268 | "rk2d", x=0.0, y=0.0, px=1.0, py=2.0, mass=1.0, potential_fn=fn, dt=0.1 269 | ) 270 | w = ctf.RK42DTask().apply(cs2)[0] 271 | self.assertAlmostEqual(w.px, 1.0, places=5) 272 | self.assertAlmostEqual(w.py, 2.0, places=5) 273 | self.assertAlmostEqual(w.x, 0.1, places=5) 274 | self.assertAlmostEqual(w.y, 0.2, places=5) 275 | 276 | # 30. SymplecticEuler2DTask constant potential 277 | def test_sympeuler2d_task_constant(self): 278 | fn = lambda x, y: 0.0 279 | cs2 = ctf.ContinuousSubstrate2D( 280 | "se2d", x=0.0, y=0.0, px=1.0, py=2.0, mass=1.0, potential_fn=fn, dt=0.1 281 | ) 282 | w = ctf.SymplecticEuler2DTask().apply(cs2)[0] 283 | self.assertAlmostEqual(w.px, 1.0, places=5) 284 | self.assertAlmostEqual(w.py, 2.0, places=5) 285 | self.assertAlmostEqual(w.x, 0.1, places=5) 286 | self.assertAlmostEqual(w.y, 0.2, places=5) 287 | 288 | # 31. graviton emission 289 | def test_graviton_emission(self): 290 | MASS = A("mass") 291 | ΔE = 3.0 292 | emit = ctf.GravitonEmissionTask(MASS, emission_energy=ΔE) 293 | cons = ctf.Constructor([emit]) 294 | m0 = ctf.Substrate("m", MASS, energy=10.0) 295 | worlds = cons.perform(m0) 296 | mass_br = [b for b in worlds if b.attr == MASS] 297 | grav_br = [b for b in worlds if b.attr == ctf.GRAVITON] 298 | self.assertEqual(len(mass_br), 1) 299 | self.assertEqual(len(grav_br), 1) 300 | self.assertAlmostEqual(mass_br[0].energy, 10.0 - ΔE, places=5) 301 | 302 | # 32. graviton absorption 303 | def test_graviton_absorption(self): 304 | MASS = A("mass") 305 | ΔE = 2.5 306 | absorb = ctf.GravitonAbsorptionTask(MASS, absorption_energy=ΔE) 307 | cons = ctf.Constructor([absorb]) 308 | g_sub = ctf.Substrate("g", ctf.GRAVITON, energy=0.0) 309 | branches = cons.perform(g_sub) 310 | self.assertEqual(len(branches), 1) 311 | self.assertEqual(branches[0].attr, MASS) 312 | self.assertAlmostEqual(branches[0].energy, ΔE, places=5) 313 | 314 | # 33. QuantumGravityConstructor end-to-end 315 | def test_quantum_gravity_constructor(self): 316 | MASS = A("mass") 317 | ΔE = 4.0 318 | qg = ctf.QuantumGravityConstructor(MASS, ΔE) 319 | m0 = ctf.Substrate("m", MASS, energy=12.0) 320 | worlds = qg.perform(m0) 321 | mass_w = [w for w in worlds if w.attr == MASS] 322 | grav_w = [w for w in worlds if w.attr == ctf.GRAVITON] 323 | self.assertEqual(len(mass_w), 1) 324 | self.assertEqual(len(grav_w), 1) 325 | self.assertAlmostEqual(mass_w[0].energy, 12.0 - ΔE, places=5) 326 | 327 | # now absorb 328 | absorb_worlds = qg.perform(grav_w[0]) 329 | self.assertEqual(absorb_worlds[0].attr, MASS) 330 | self.assertAlmostEqual(absorb_worlds[0].energy, ΔE, places=5) 331 | 332 | # 34. photon emission 333 | def test_photon_emission(self): 334 | ELEC = A("charge_site") 335 | ΔE = 5.0 336 | emit = ctf.PhotonEmissionTask(ELEC, emission_energy=ΔE) 337 | cons = ctf.Constructor([emit]) 338 | s0 = ctf.Substrate("S", ELEC, energy=20.0) 339 | worlds = cons.perform(s0) 340 | # one world with reduced energy, one photon 341 | self.assertEqual(len(worlds), 2) 342 | sch, ph = sorted(worlds, key=lambda w: w.attr.label) 343 | self.assertEqual(ph.attr, ctf.PHOTON) 344 | self.assertAlmostEqual(sch.energy, 20.0 - ΔE, places=6) 345 | 346 | # 35. photon absorption 347 | def test_photon_absorption(self): 348 | ELEC = A("charge_site") 349 | ΔE = 2.5 350 | absrt = ctf.PhotonAbsorptionTask(ELEC, absorption_energy=ΔE) 351 | cons = ctf.Constructor([absrt]) 352 | ph = ctf.Substrate("γ", ctf.PHOTON, energy=0.0) 353 | worlds = cons.perform(ph) 354 | self.assertEqual(len(worlds), 1) 355 | self.assertEqual(worlds[0].attr, ELEC) 356 | self.assertAlmostEqual(worlds[0].energy, ΔE, places=6) 357 | 358 | # 36. Coulomb coupling 1D 359 | def test_coulomb_coupling_1d(self): 360 | # two positive charges repel 361 | cs1 = ctf.ContinuousSubstrate( 362 | "c1", -1.0, 0.0, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1, charge=+1 363 | ) 364 | cs2 = ctf.ContinuousSubstrate( 365 | "c2", +1.0, 0.0, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1, charge=+1 366 | ) 367 | task = ctf.MultiSubstrateTask( 368 | "coulomb", [cs1.attr, cs2.attr], ctf.coulomb_coupling_fn 369 | ) 370 | mc = ctf.MultiConstructor([task]) 371 | out1, out2 = mc.perform([cs1, cs2])[0] 372 | # they should have opposite momentum changes 373 | self.assertNotEqual(out1.p, cs1.p) 374 | self.assertAlmostEqual(out1.p, -out2.p, places=6) 375 | 376 | # 37. Lorentz force coupling (2D) 377 | def test_lorentz_force_coupling_2d(self): 378 | # set up a charged 2D substrate 379 | cs = ctf.ContinuousSubstrate2D( 380 | "e-", 381 | x=0.0, 382 | y=1.0, 383 | px=2.0, 384 | py=3.0, 385 | mass=1.0, 386 | potential_fn=lambda x, y: 0.0, 387 | dt=0.1, 388 | charge=+1, 389 | ) 390 | # uniform Bz = 0.5 outward 391 | B = ctf.FieldSubstrate("B", Bz=0.5) 392 | # wire up the multi‐substrate lorentz task 393 | task = ctf.MultiSubstrateTask( 394 | "lorentz", [cs.attr, B.attr], ctf.lorentz_coupling_fn 395 | ) 396 | mc = ctf.MultiConstructor([task]) 397 | 398 | out_cs, out_B = mc.perform([cs, B])[0] 399 | 400 | # expected impulses 401 | expected_Fx = cs.charge * cs.py * B.Bz 402 | expected_Fy = -cs.charge * cs.px * B.Bz 403 | 404 | self.assertAlmostEqual(out_cs.px, cs.px + expected_Fx * cs.dt, places=6) 405 | self.assertAlmostEqual(out_cs.py, cs.py + expected_Fy * cs.dt, places=6) 406 | # B‐field should be unchanged 407 | self.assertEqual(out_B.Bz, B.Bz) 408 | 409 | 410 | # 38. Hydrogen atom excitation and deexcitation 411 | def test_hydrogen_excitation_cycle(self): 412 | gap = 10.2 413 | cons = ctf.HydrogenAtomConstructor(gap) 414 | h = ctf.Substrate("H", ctf.HYDROGEN_GROUND, energy=0.0) 415 | excited = cons.perform(h)[0] 416 | self.assertEqual(excited.attr, ctf.HYDROGEN_EXCITED) 417 | self.assertAlmostEqual(excited.energy, gap, places=6) 418 | worlds = cons.perform(excited) 419 | attrs = {w.attr for w in worlds} 420 | self.assertIn(ctf.HYDROGEN_GROUND, attrs) 421 | self.assertIn(ctf.PHOTON, attrs) 422 | 423 | # 39. Hydrogen collision leading to molecule 424 | def test_hydrogen_collision_bond(self): 425 | hi = ctf.HydrogenInteractionConstructor(bond_energy=4.5) 426 | h1 = ctf.Substrate("h1", ctf.HYDROGEN_GROUND, energy=3.0) 427 | h2 = ctf.Substrate("h2", ctf.HYDROGEN_GROUND, energy=3.0) 428 | result = hi.perform([h1, h2])[0] 429 | self.assertEqual(len(result), 1) 430 | self.assertEqual(result[0].attr, ctf.HYDROGEN_MOLECULE) 431 | 432 | # 40. Hydrogen collision insufficient energy 433 | def test_hydrogen_collision_no_bond(self): 434 | hi = ctf.HydrogenInteractionConstructor(bond_energy=4.5) 435 | h1 = ctf.Substrate("ha", ctf.HYDROGEN_GROUND, energy=1.0) 436 | h2 = ctf.Substrate("hb", ctf.HYDROGEN_GROUND, energy=1.0) 437 | result = hi.perform([h1, h2])[0] 438 | self.assertEqual(len(result), 2) 439 | self.assertEqual({s.attr for s in result}, {ctf.HYDROGEN_GROUND}) 440 | 441 | # 41. Backend registry functionality 442 | def test_backend_registry(self): 443 | # Create a new registry for testing 444 | registry = ctf.BackendRegistry() 445 | 446 | # Create a simple test backend 447 | test_backend = ctf.ElectromagnetismBackend(A("test_charge"), 2.0) 448 | 449 | # Register the backend 450 | registry.register(test_backend) 451 | 452 | # Test retrieval 453 | retrieved = registry.get_backend("electromagnetism") 454 | self.assertEqual(retrieved.get_name(), "electromagnetism") 455 | 456 | # Test listing 457 | backends = registry.list_backends() 458 | self.assertIn("electromagnetism", backends) 459 | 460 | # Test getting all tasks 461 | tasks = registry.get_all_tasks(["electromagnetism"]) 462 | self.assertEqual(len(tasks), 2) # emission + absorption 463 | self.assertTrue(all(isinstance(t, ctf.Task) for t in tasks)) 464 | 465 | # 42. Global registry default backends 466 | def test_global_registry_defaults(self): 467 | registry = ctf.get_global_registry() 468 | expected_backends = [ 469 | "electromagnetism", 470 | "quantum_gravity", 471 | "hydrogen_atoms", 472 | "continuous_dynamics" 473 | ] 474 | 475 | for backend_name in expected_backends: 476 | self.assertIn(backend_name, registry.list_backends()) 477 | backend = registry.get_backend(backend_name) 478 | self.assertIsInstance(backend, ctf.TaskOntologyBackend) 479 | tasks = backend.get_tasks() 480 | self.assertTrue(len(tasks) > 0) 481 | 482 | # 43. UniversalConstructor with backends 483 | def test_universal_constructor_with_backends(self): 484 | uc = ctf.UniversalConstructor() 485 | 486 | # Test building from specific backends 487 | em_constructor = uc.build_from_backends(["electromagnetism"]) 488 | self.assertIsInstance(em_constructor, ctf.Constructor) 489 | 490 | # Test with single backend 491 | qg_backend = ctf.QuantumGravityBackend(A("mass"), 5.0) 492 | qg_constructor = uc.build_with_backend(qg_backend) 493 | self.assertIsInstance(qg_constructor, ctf.Constructor) 494 | 495 | # Test that tasks work 496 | mass_sub = ctf.Substrate("m", A("mass"), energy=10.0) 497 | worlds = qg_constructor.perform(mass_sub) 498 | self.assertEqual(len(worlds), 2) # mass + graviton 499 | 500 | # 44. Backend task execution 501 | def test_backend_task_execution(self): 502 | # Test electromagnetism backend 503 | em_backend = ctf.ElectromagnetismBackend(A("charge"), 3.0) 504 | uc = ctf.UniversalConstructor() 505 | em_constructor = uc.build_with_backend(em_backend) 506 | 507 | charge_sub = ctf.Substrate("e", A("charge"), energy=10.0) 508 | worlds = em_constructor.perform(charge_sub) 509 | self.assertEqual(len(worlds), 2) # charge + photon 510 | 511 | # Find photon and test absorption 512 | photon = next(w for w in worlds if w.attr == ctf.PHOTON) 513 | absorbed = em_constructor.perform(photon) 514 | self.assertEqual(len(absorbed), 1) 515 | self.assertEqual(absorbed[0].attr, A("charge")) 516 | 517 | # 45. Multiple backend combination 518 | def test_multiple_backend_combination(self): 519 | uc = ctf.UniversalConstructor() 520 | 521 | # Build constructor with multiple backends 522 | multi_constructor = uc.build_from_backends([ 523 | "electromagnetism", 524 | "quantum_gravity" 525 | ]) 526 | 527 | # Test with charge substrate 528 | charge_sub = ctf.Substrate("e", A("charge_site"), energy=20.0) 529 | em_worlds = multi_constructor.perform(charge_sub) 530 | self.assertEqual(len(em_worlds), 2) # charge + photon 531 | 532 | # Test with mass substrate 533 | mass_sub = ctf.Substrate("m", A("mass"), energy=15.0) 534 | qg_worlds = multi_constructor.perform(mass_sub) 535 | self.assertEqual(len(qg_worlds), 2) # mass + graviton 536 | 537 | # 46. Custom backend creation 538 | def test_custom_backend_creation(self): 539 | # Create a custom backend 540 | class TestBackend(ctf.TaskOntologyBackend): 541 | def get_tasks(self): 542 | return [ 543 | ctf.Task("test_task", A("test_in"), [(A("test_out"), 0, 0)]) 544 | ] 545 | 546 | def get_name(self) -> str: 547 | return "test_backend" 548 | 549 | custom_backend = TestBackend() 550 | uc = ctf.UniversalConstructor() 551 | custom_constructor = uc.build_with_backend(custom_backend) 552 | 553 | test_sub = ctf.Substrate("test", A("test_in"), energy=1.0) 554 | result = custom_constructor.perform(test_sub) 555 | self.assertEqual(len(result), 1) 556 | self.assertEqual(result[0].attr, A("test_out")) 557 | 558 | # 47. Backend error handling 559 | def test_backend_error_handling(self): 560 | registry = ctf.BackendRegistry() 561 | 562 | # Test getting non-existent backend 563 | with self.assertRaises(ValueError): 564 | registry.get_backend("non_existent") 565 | 566 | # Test that error message contains backend name 567 | try: 568 | registry.get_backend("missing_backend") 569 | except ValueError as e: 570 | self.assertIn("missing_backend", str(e)) 571 | 572 | # 48. Test __repr__ methods for coverage 573 | def test_attribute_repr(self): 574 | attr = A("test_attr") 575 | repr_str = repr(attr) 576 | self.assertIn("test_attr", repr_str) 577 | self.assertIn("〈", repr_str) 578 | self.assertIn("〉", repr_str) 579 | 580 | # 49. Test Substrate __repr__ method 581 | def test_substrate_repr(self): 582 | s = ctf.Substrate("test_sub", A("test_attr"), energy=5.0, charge=2, clock=3) 583 | repr_str = repr(s) 584 | self.assertIn("test_sub", repr_str) 585 | self.assertIn("test_attr", repr_str) 586 | self.assertIn("E=5.0", repr_str) 587 | self.assertIn("Q=2", repr_str) 588 | self.assertIn("t=3", repr_str) 589 | 590 | # 50. Test Task.apply early return for impossible tasks 591 | def test_task_apply_impossible(self): 592 | task = ctf.Task("test", A("input"), [(A("output"), 0, 0)]) 593 | wrong_substrate = ctf.Substrate("wrong", A("different"), energy=1.0) 594 | result = task.apply(wrong_substrate) 595 | self.assertEqual(result, []) 596 | 597 | # 51. Test Task.apply energy constraint (negative energy result) 598 | def test_task_apply_negative_energy(self): 599 | # Create a task that would result in negative energy 600 | task = ctf.Task("drain", A("source"), [(A("empty"), -10.0, 0)]) 601 | low_energy_sub = ctf.Substrate("low", A("source"), energy=5.0) 602 | result = task.apply(low_energy_sub) 603 | # Should skip the output that would result in negative energy 604 | self.assertEqual(len(result), 0) 605 | 606 | # 52. Test Constructor early return for no matching tasks 607 | def test_constructor_no_matching_tasks(self): 608 | task = ctf.Task("specific", A("specific_input"), [(A("output"), 0, 0)]) 609 | cons = ctf.Constructor([task]) 610 | unmatched_sub = ctf.Substrate("unmatched", A("different_input"), energy=1.0) 611 | result = cons.perform(unmatched_sub) 612 | # Should return the original substrate unchanged 613 | self.assertEqual(len(result), 1) 614 | self.assertIs(result[0], unmatched_sub) 615 | 616 | # 53. Test SwapConstructor error handling 617 | def test_swap_constructor_error(self): 618 | a = ctf.Substrate("a", A("type1"), 1.0, fungible_id="id1") 619 | b = ctf.Substrate("b", A("type2"), 1.0, fungible_id="id2") 620 | 621 | # Should raise ValueError for non-fungible substrates 622 | with self.assertRaises(ValueError) as cm: 623 | ctf.SwapConstructor.swap(a, b) 624 | self.assertIn("not fungible", str(cm.exception)) 625 | 626 | # 54. Test plot_phase_space (covered by existing test or matplotlib absence) 627 | def test_plot_phase_space_simple(self): 628 | # Simple test that just ensures the function can be called 629 | cs1 = ctf.ContinuousSubstrate("test", 0.0, 1.0, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1) 630 | cs2 = ctf.ContinuousSubstrate("test", 0.1, 0.9, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1) 631 | # This should not raise an error 632 | try: 633 | ctf.plot_phase_space({"test": [cs1, cs2]}) 634 | except ImportError: 635 | # This path is covered when matplotlib is not available 636 | pass 637 | 638 | # 58. Test plot_phase_space with matplotlib available 639 | @unittest.skipUnless(True, "matplotlib is now available") # Always run since we installed it 640 | def test_plot_phase_space_matplotlib(self): 641 | import matplotlib.pyplot as plt 642 | # Mock plt.show to prevent actual display 643 | original_show = plt.show 644 | plt.show = lambda: None 645 | 646 | try: 647 | cs1 = ctf.ContinuousSubstrate("traj", 0.0, 1.0, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1) 648 | cs2 = ctf.ContinuousSubstrate("traj", 0.1, 0.9, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1) 649 | # This should exercise the matplotlib code path 650 | ctf.plot_phase_space({"test_trajectory": [cs1, cs2]}) 651 | finally: 652 | plt.show = original_show 653 | 654 | # 68. Test plot_phase_space ImportError case by mocking 655 | def test_plot_phase_space_import_error(self): 656 | import sys 657 | import io 658 | from unittest.mock import patch 659 | 660 | # Capture print output 661 | captured_output = io.StringIO() 662 | 663 | with patch('builtins.__import__', side_effect=ImportError): 664 | with patch('sys.stdout', captured_output): 665 | cs1 = ctf.ContinuousSubstrate("test", 0.0, 1.0, mass=1.0, potential_fn=lambda x: 0.0, dt=0.1) 666 | # This should trigger the ImportError path 667 | ctf.plot_phase_space({"test": [cs1]}) 668 | 669 | # Check that the expected message was printed 670 | output = captured_output.getvalue() 671 | self.assertIn("matplotlib not available", output) 672 | 673 | # 55. Test continuous dynamics task early returns 674 | def test_dynamics_task_impossible(self): 675 | # Test DynamicsTask with wrong substrate type 676 | task = ctf.DynamicsTask() 677 | wrong_sub = ctf.Substrate("wrong", A("not_continuous"), energy=1.0) 678 | result = task.apply(wrong_sub) 679 | self.assertEqual(result, []) 680 | 681 | # 56. Test RK4Task impossible case 682 | def test_rk4_task_impossible(self): 683 | task = ctf.RK4Task() 684 | wrong_sub = ctf.Substrate("wrong", A("not_continuous"), energy=1.0) 685 | result = task.apply(wrong_sub) 686 | self.assertEqual(result, []) 687 | 688 | # 57. Test SymplecticEulerTask impossible case 689 | def test_symplectic_euler_impossible(self): 690 | task = ctf.SymplecticEulerTask() 691 | wrong_sub = ctf.Substrate("wrong", A("not_continuous"), energy=1.0) 692 | result = task.apply(wrong_sub) 693 | self.assertEqual(result, []) 694 | 695 | # 59. Test 2D dynamics tasks impossible cases 696 | def test_dynamics2d_task_impossible(self): 697 | task = ctf.Dynamics2DTask() 698 | wrong_sub = ctf.Substrate("wrong", A("not_2d"), energy=1.0) 699 | result = task.apply(wrong_sub) 700 | self.assertEqual(result, []) 701 | 702 | # 60. Test RK42DTask impossible case 703 | def test_rk42d_task_impossible(self): 704 | task = ctf.RK42DTask() 705 | wrong_sub = ctf.Substrate("wrong", A("not_2d"), energy=1.0) 706 | result = task.apply(wrong_sub) 707 | self.assertEqual(result, []) 708 | 709 | # 61. Test SymplecticEuler2DTask impossible case 710 | def test_symplectic_euler2d_impossible(self): 711 | task = ctf.SymplecticEuler2DTask() 712 | wrong_sub = ctf.Substrate("wrong", A("not_2d"), energy=1.0) 713 | result = task.apply(wrong_sub) 714 | self.assertEqual(result, []) 715 | 716 | # 62. Test PhotonEmissionTask with carry_residual=True 717 | def test_photon_emission_carry_residual(self): 718 | ELEC = A("charge_site") 719 | emit = ctf.PhotonEmissionTask(ELEC, emission_energy=5.0, carry_residual=True) 720 | cons = ctf.Constructor([emit]) 721 | s0 = ctf.Substrate("S", ELEC, energy=20.0) 722 | worlds = cons.perform(s0) 723 | self.assertEqual(len(worlds), 2) 724 | # Find the photon - it should carry the original energy 725 | photon = next(w for w in worlds if w.attr == ctf.PHOTON) 726 | self.assertEqual(photon.energy, 20.0) # Original energy carried 727 | 728 | # 63. Test EMConstructor 729 | def test_em_constructor(self): 730 | ELEC = A("charge") 731 | em_cons = ctf.EMConstructor(ELEC, ΔE=3.0) 732 | charge_sub = ctf.Substrate("e", ELEC, energy=10.0) 733 | worlds = em_cons.perform(charge_sub) 734 | self.assertEqual(len(worlds), 2) # charge + photon 735 | 736 | # 64. Test BackendRegistry get_all_tasks with None 737 | def test_backend_registry_get_all_tasks_none(self): 738 | registry = ctf.BackendRegistry() 739 | em_backend = ctf.ElectromagnetismBackend(A("charge"), 2.0) 740 | registry.register(em_backend) 741 | 742 | # Test with None parameter (should get all) 743 | all_tasks = registry.get_all_tasks(None) 744 | self.assertTrue(len(all_tasks) > 0) 745 | 746 | # 65. Test UniversalConstructor.build method directly 747 | def test_universal_constructor_build(self): 748 | uc = ctf.UniversalConstructor() 749 | task = ctf.Task("test", A("in"), [(A("out"), 0, 0)]) 750 | cons = uc.build([task]) 751 | self.assertIsInstance(cons, ctf.Constructor) 752 | 753 | # 66. Test TaskOntologyBackend.get_description 754 | def test_backend_get_description(self): 755 | em_backend = ctf.ElectromagnetismBackend(A("charge"), 2.0) 756 | description = em_backend.get_description() 757 | self.assertIn("electromagnetism", description) 758 | self.assertIn("backend", description) 759 | 760 | # 67. Test HydrogenDeexcitationTask impossible case 761 | def test_hydrogen_deexcitation_impossible(self): 762 | task = ctf.HydrogenDeexcitationTask(energy_gap=10.2) 763 | wrong_sub = ctf.Substrate("wrong", A("not_hydrogen"), energy=1.0) 764 | result = task.apply(wrong_sub) 765 | self.assertEqual(result, []) 766 | 767 | 768 | if __name__ == "__main__": 769 | unittest.main() 770 | -------------------------------------------------------------------------------- /ct_framework.py: -------------------------------------------------------------------------------- 1 | """ 2 | ct_framework.py · Constructor-Theory mini-framework 3 | May 2025 · Includes: 4 | • Core Task/Constructor, NullConstructor 5 | • Timer/Clock Constructors 6 | • Fungible Swap + ASCII visualization 7 | • 1D & 2D Continuous dynamics + multiple integrators 8 | • Multi-substrate coupling (gravitation, Coulomb, Lorentz) 9 | • Quantum branching: decoherence, Mach–Zehnder 10 | • Quantum-Gravity: graviton emission & absorption 11 | • Electromagnetism: photon emission & absorption 12 | • UniversalConstructor bootstrap support 13 | """ 14 | 15 | import math 16 | import time 17 | from typing import List, Tuple, Dict, Optional, Callable 18 | 19 | # ── 1. Core datatypes ─────────────────────────────────────────────────── 20 | 21 | 22 | class Attribute: 23 | def __init__(self, label: str): 24 | self.label = label 25 | 26 | def __hash__(self): 27 | return hash(self.label) 28 | 29 | def __eq__(self, other): 30 | return isinstance(other, Attribute) and other.label == self.label 31 | 32 | def __repr__(self): 33 | return f"〈{self.label}〉" 34 | 35 | 36 | class Substrate: 37 | def __init__( 38 | self, 39 | name: str, 40 | attr: Attribute, 41 | energy: float, 42 | charge: int = 0, 43 | clock: int = 0, 44 | velocity: float = 0.0, 45 | grav: float = 0.0, 46 | fungible_id: Optional[str] = None, 47 | entangled_with: Optional["Substrate"] = None, 48 | ): 49 | self.name = name 50 | self.attr = attr 51 | self.energy, self.charge, self.clock = energy, charge, clock 52 | self.velocity, self.grav = velocity, grav 53 | self.fungible_id = fungible_id or attr.label 54 | self.entangled_with = entangled_with 55 | self._locked = False 56 | 57 | def adjusted_duration(self, τ: float) -> float: 58 | c = 299_792_458 59 | if self.velocity: 60 | β = self.velocity / c 61 | γ = 1 / math.sqrt(1 - β * β) 62 | return τ / γ 63 | if self.grav: 64 | return τ * (1 + self.grav / c**2) 65 | return τ 66 | 67 | def is_fungible_with(self, other: "Substrate") -> bool: 68 | return self.attr == other.attr and self.fungible_id == other.fungible_id 69 | 70 | def clone(self) -> "Substrate": 71 | w = Substrate( 72 | self.name, 73 | self.attr, 74 | self.energy, 75 | self.charge, 76 | self.clock, 77 | self.velocity, 78 | self.grav, 79 | self.fungible_id, 80 | self.entangled_with, 81 | ) 82 | w._locked = self._locked 83 | return w 84 | 85 | def evolve_to(self, attr: Attribute): 86 | self.attr = attr 87 | 88 | def __repr__(self): 89 | return ( 90 | f"{self.name}:{self.attr.label}" 91 | f"(E={self.energy},Q={self.charge},t={self.clock}," 92 | f"F={self.fungible_id})" 93 | ) 94 | 95 | 96 | # quantum‐carrier attributes 97 | GRAVITON = Attribute("graviton") 98 | PHOTON = Attribute("photon") 99 | 100 | 101 | # ── 2. Task & Constructor ──────────────────────────────────────────────── 102 | 103 | 104 | class Task: 105 | def __init__( 106 | self, 107 | name: str, 108 | input_attr: Attribute, 109 | outputs: List[Tuple[Attribute, float, int]], 110 | duration: float = 0.0, 111 | quantum: bool = False, 112 | irreversible: bool = False, 113 | clock_inc: int = 0, 114 | action_cost: float = 0.0, 115 | ): 116 | self.name = name 117 | self.input_attr = input_attr 118 | self.outputs = outputs 119 | self.duration = duration 120 | self.quantum = quantum 121 | self.irreversible = irreversible 122 | self.clock_inc = clock_inc 123 | self.action_cost = action_cost 124 | 125 | def possible(self, s: Substrate) -> bool: 126 | return ( 127 | not getattr(s, "_locked", False) 128 | and s.attr == self.input_attr 129 | and all(s.charge + dq == s.charge for _, _, dq in self.outputs) 130 | ) 131 | 132 | def apply(self, s: Substrate) -> List[Substrate]: 133 | if not self.possible(s): 134 | return [] 135 | time.sleep(min(s.adjusted_duration(self.duration), 0.004)) 136 | orig = s.clone() 137 | # mutate real substrate for classical irreversible tasks 138 | if self.irreversible and not self.quantum: 139 | out_attr, dE, dQ = self.outputs[0] 140 | s.evolve_to(out_attr) 141 | s.energy += dE 142 | s.charge += dQ 143 | s.clock += self.clock_inc 144 | s._locked = True 145 | 146 | worlds: List[Substrate] = [] 147 | for attr, dE, dQ in self.outputs: 148 | if orig.energy + dE < 0: 149 | continue 150 | w = orig.clone() 151 | w.attr = attr 152 | # special‐case carrier quanta 153 | if attr is GRAVITON: 154 | # gravitons carry zero energy 155 | w.energy = 0.0 156 | elif attr is PHOTON: 157 | # photons carry the emitter’s residual energy 158 | w.energy = orig.energy 159 | else: 160 | w.energy = orig.energy + dE 161 | w.charge = orig.charge + dQ 162 | w.clock = orig.clock + self.clock_inc 163 | if self.irreversible and not self.quantum: 164 | w._locked = True 165 | if self.quantum: 166 | w.entangled_with = orig.entangled_with 167 | worlds.append(w) 168 | return worlds 169 | 170 | 171 | class Constructor: 172 | def __init__(self, tasks: List[Task]): 173 | self.tasks_by_input: Dict[str, List[Task]] = {} 174 | for t in tasks: 175 | self.tasks_by_input.setdefault(t.input_attr.label, []).append(t) 176 | 177 | def perform(self, s: Substrate) -> List[Substrate]: 178 | if getattr(s, "_locked", False): 179 | return [] 180 | candidates = self.tasks_by_input.get(s.attr.label, []) 181 | if not candidates: 182 | return [s] 183 | worlds: List[Substrate] = [] 184 | for t in candidates: 185 | worlds.extend(t.apply(s)) 186 | return worlds 187 | 188 | 189 | class ActionConstructor(Constructor): 190 | def __init__(self, tasks: List[Task]): 191 | super().__init__(tasks) 192 | self.tasks_by_input = { 193 | label: [min(ts, key=lambda t: t.action_cost)] 194 | for label, ts in self.tasks_by_input.items() 195 | } 196 | 197 | 198 | class NullConstructor(Constructor): 199 | def __init__(self): 200 | super().__init__([]) 201 | 202 | def perform(self, s: Substrate) -> List[Substrate]: 203 | return [s] 204 | 205 | 206 | # ── 3. Timer & Clock Constructors ─────────────────────────────────────── 207 | 208 | 209 | class TimerSubstrate(Substrate): 210 | def __init__(self, name: str, period: float): 211 | super().__init__(name, Attribute("start"), energy=0.0) 212 | self.period = period 213 | self._t0 = time.time() 214 | 215 | 216 | class TimerConstructor(Constructor): 217 | START, RUN, HALT = Attribute("start"), Attribute("running"), Attribute("halted") 218 | 219 | def __init__(self): 220 | super().__init__([]) 221 | 222 | def perform(self, t: TimerSubstrate) -> List[Substrate]: 223 | dt = time.time() - t._t0 224 | if t.attr == self.START: 225 | t.attr = self.RUN 226 | if t.attr == self.RUN and dt >= t.period: 227 | t.attr = self.HALT 228 | return [t] 229 | 230 | 231 | class ClockConstructor: 232 | def __init__(self, tick: float): 233 | self.tick = tick 234 | 235 | def perform(self, s: Substrate) -> List[Substrate]: 236 | time.sleep(min(s.adjusted_duration(self.tick), 0.004)) 237 | s.clock += 1 238 | return [s] 239 | 240 | 241 | # ── 4. Fungible swap & ASCII visualiser ───────────────────────────────── 242 | 243 | 244 | class SwapConstructor: 245 | @staticmethod 246 | def swap(a: Substrate, b: Substrate): 247 | if not a.is_fungible_with(b): 248 | raise ValueError("Substrates not fungible") 249 | a.name, b.name = b.name, a.name 250 | return a, b 251 | 252 | 253 | def ascii_branch(worlds: List[Substrate]) -> str: 254 | return "\n".join(f"* {w.attr.label} ({w.name})" for w in worlds) 255 | 256 | 257 | # ── 5. Phase-space visualiser ─────────────────────────────────────────── 258 | 259 | 260 | def plot_phase_space(trajectories: Dict[str, List["ContinuousSubstrate"]]): 261 | try: 262 | import matplotlib.pyplot as _plt 263 | except ImportError: 264 | print("matplotlib not available; skipping plot.") 265 | return 266 | for label, traj in trajectories.items(): 267 | xs = [s.x for s in traj] 268 | ps = [s.p for s in traj] 269 | _plt.figure() 270 | _plt.plot(xs, ps) 271 | _plt.title(f"Phase space: {label}") 272 | _plt.xlabel("x") 273 | _plt.ylabel("p") 274 | _plt.show() 275 | 276 | 277 | # ── 6. 1D Continuous dynamics & integrators ───────────────────────────── 278 | 279 | 280 | def finite_diff(f: Callable[[float], float], x: float, h: float) -> float: 281 | return (f(x + h) - f(x - h)) / (2 * h) 282 | 283 | 284 | class ContinuousSubstrate(Substrate): 285 | def __init__( 286 | self, 287 | name: str, 288 | x: float, 289 | p: float, 290 | mass: float, 291 | potential_fn: Callable[[float], float], 292 | dt: float, 293 | **kwargs, 294 | ): 295 | super().__init__(name, Attribute("dynamic"), energy=0.0, **kwargs) 296 | self.x, self.p, self.mass = x, p, mass 297 | self.potential_fn, self.dt = potential_fn, dt 298 | 299 | def clone(self) -> "ContinuousSubstrate": 300 | w = ContinuousSubstrate( 301 | self.name, 302 | self.x, 303 | self.p, 304 | self.mass, 305 | self.potential_fn, 306 | self.dt, 307 | fungible_id=self.fungible_id, 308 | entangled_with=self.entangled_with, 309 | ) 310 | w.energy, w.charge, w.clock = self.energy, self.charge, self.clock 311 | w.velocity, w.grav = self.velocity, self.grav 312 | w._locked = self._locked 313 | return w 314 | 315 | 316 | class DynamicsTask(Task): 317 | def __init__(self): 318 | super().__init__( 319 | "dynamics", 320 | Attribute("dynamic"), 321 | [(Attribute("dynamic"), 0, 0)], 322 | clock_inc=1, 323 | ) 324 | 325 | def apply(self, s: ContinuousSubstrate) -> List[Substrate]: 326 | if not self.possible(s): 327 | return [] 328 | force = -finite_diff(s.potential_fn, s.x, s.dt) 329 | p_new = s.p + force * s.dt 330 | x_new = s.x + (p_new / s.mass) * s.dt 331 | w = s.clone() 332 | w.p, w.x, w.clock = p_new, x_new, w.clock + 1 333 | return [w] 334 | 335 | 336 | class RK4Task(Task): 337 | def __init__(self): 338 | super().__init__( 339 | "rk4", Attribute("dynamic"), [(Attribute("dynamic"), 0, 0)], clock_inc=1 340 | ) 341 | 342 | def apply(self, s: ContinuousSubstrate) -> List[Substrate]: 343 | if not self.possible(s): 344 | return [] 345 | dt, f = s.dt, s.potential_fn 346 | 347 | def deriv(x, p): 348 | return p / s.mass, -finite_diff(f, x, dt) 349 | 350 | k1x, k1p = deriv(s.x, s.p) 351 | k2x, k2p = deriv(s.x + k1x * dt / 2, s.p + k1p * dt / 2) 352 | k3x, k3p = deriv(s.x + k2x * dt / 2, s.p + k2p * dt / 2) 353 | k4x, k4p = deriv(s.x + k3x * dt, s.p + k3p * dt) 354 | x_new = s.x + (k1x + 2 * k2x + 2 * k3x + k4x) * dt / 6 355 | p_new = s.p + (k1p + 2 * k2p + 2 * k3p + k4p) * dt / 6 356 | w = s.clone() 357 | w.x, w.p, w.clock = x_new, p_new, w.clock + 1 358 | return [w] 359 | 360 | 361 | class SymplecticEulerTask(Task): 362 | def __init__(self): 363 | super().__init__( 364 | "symp_euler", 365 | Attribute("dynamic"), 366 | [(Attribute("dynamic"), 0, 0)], 367 | clock_inc=1, 368 | ) 369 | 370 | def apply(self, s: ContinuousSubstrate) -> List[Substrate]: 371 | if not self.possible(s): 372 | return [] 373 | dt = s.dt 374 | force = -finite_diff(s.potential_fn, s.x, s.dt) 375 | p_new = s.p + force * dt 376 | x_new = s.x + (p_new / s.mass) * dt 377 | w = s.clone() 378 | w.p, w.x, w.clock = p_new, x_new, w.clock + 1 379 | return [w] 380 | 381 | 382 | # ── 7. Multi-substrate Tasks & coupling ──────────────────────────────── 383 | 384 | 385 | class MultiSubstrateTask: 386 | def __init__( 387 | self, 388 | name: str, 389 | input_attrs: List[Attribute], 390 | apply_fn: Callable[[List[Substrate]], List[List[Substrate]]], 391 | duration: float = 0.0, 392 | ): 393 | self.name = name 394 | self.input_attrs = input_attrs 395 | self.apply_fn = apply_fn 396 | self.duration = duration 397 | 398 | def possible(self, substrates: List[Substrate]) -> bool: 399 | return all(sub.attr == inp for sub, inp in zip(substrates, self.input_attrs)) 400 | 401 | def apply(self, substrates: List[Substrate]) -> List[List[Substrate]]: 402 | time.sleep(min(substrates[0].adjusted_duration(self.duration), 0.004)) 403 | return self.apply_fn(substrates) 404 | 405 | 406 | class MultiConstructor: 407 | def __init__(self, tasks: List[MultiSubstrateTask]): 408 | self.tasks = tasks 409 | 410 | def perform(self, substrates: List[Substrate]) -> List[List[Substrate]]: 411 | results: List[List[Substrate]] = [] 412 | for t in self.tasks: 413 | if t.possible(substrates): 414 | results.extend(t.apply(substrates)) 415 | return results 416 | 417 | 418 | def grav_coupling_fn(subs: List[Substrate]) -> List[List[Substrate]]: 419 | s1, s2 = subs 420 | G = 6.67430e-11 421 | r = abs(s2.x - s1.x) 422 | F = G * s1.mass * s2.mass / (r * r if r else 1.0) 423 | dir12 = (s2.x - s1.x) / r if r else 1.0 424 | s1n, s2n = s1.clone(), s2.clone() 425 | dt = s1.dt 426 | s1n.p += F * dir12 * dt 427 | s2n.p -= F * dir12 * dt 428 | s1n.clock += 1 429 | s2n.clock += 1 430 | return [[s1n, s2n]] 431 | 432 | 433 | # ── 8. 2D Continuous & integrators ───────────────────────────────────── 434 | 435 | 436 | def finite_diff_x( 437 | f: Callable[[float, float], float], x: float, y: float, h: float 438 | ) -> float: 439 | return (f(x + h, y) - f(x - h, y)) / (2 * h) 440 | 441 | 442 | def finite_diff_y( 443 | f: Callable[[float, float], float], x: float, y: float, h: float 444 | ) -> float: 445 | return (f(x, y + h) - f(x, y - h)) / (2 * h) 446 | 447 | 448 | class ContinuousSubstrate2D(Substrate): 449 | def __init__( 450 | self, 451 | name: str, 452 | x: float, 453 | y: float, 454 | px: float, 455 | py: float, 456 | mass: float, 457 | potential_fn: Callable[[float, float], float], 458 | dt: float, 459 | **kwargs, 460 | ): 461 | super().__init__(name, Attribute("dynamic2d"), energy=0.0, **kwargs) 462 | self.x, self.y, self.px, self.py, self.mass = x, y, px, py, mass 463 | self.potential2d, self.dt = potential_fn, dt 464 | 465 | def clone(self) -> "ContinuousSubstrate2D": 466 | w = ContinuousSubstrate2D( 467 | self.name, 468 | self.x, 469 | self.y, 470 | self.px, 471 | self.py, 472 | self.mass, 473 | self.potential2d, 474 | self.dt, 475 | fungible_id=self.fungible_id, 476 | entangled_with=self.entangled_with, 477 | ) 478 | w.energy, w.charge, w.clock = self.energy, self.charge, self.clock 479 | w.velocity, w.grav = self.velocity, self.grav 480 | w._locked = self._locked 481 | return w 482 | 483 | 484 | class Dynamics2DTask(Task): 485 | def __init__(self): 486 | super().__init__( 487 | "dynamics2d", 488 | Attribute("dynamic2d"), 489 | [(Attribute("dynamic2d"), 0, 0)], 490 | clock_inc=1, 491 | ) 492 | 493 | def apply(self, s: ContinuousSubstrate2D) -> List[Substrate]: 494 | if not self.possible(s): 495 | return [] 496 | fx = -finite_diff_x(s.potential2d, s.x, s.y, s.dt) 497 | fy = -finite_diff_y(s.potential2d, s.x, s.y, s.dt) 498 | px_new = s.px + fx * s.dt 499 | py_new = s.py + fy * s.dt 500 | x_new = s.x + (px_new / s.mass) * s.dt 501 | y_new = s.y + (py_new / s.mass) * s.dt 502 | w = s.clone() 503 | w.px, w.py, w.x, w.y = px_new, py_new, x_new, y_new 504 | w.clock += 1 505 | return [w] 506 | 507 | 508 | class RK42DTask(Task): 509 | def __init__(self): 510 | super().__init__( 511 | "rk42d", 512 | Attribute("dynamic2d"), 513 | [(Attribute("dynamic2d"), 0, 0)], 514 | clock_inc=1, 515 | ) 516 | 517 | def apply(self, s: ContinuousSubstrate2D) -> List[Substrate]: 518 | if not self.possible(s): 519 | return [] 520 | dt, f = s.dt, s.potential2d 521 | 522 | def deriv(x, y, px, py): 523 | fx = -finite_diff_x(f, x, y, dt) 524 | fy = -finite_diff_y(f, x, y, dt) 525 | return px / s.mass, py / s.mass, fx, fy 526 | 527 | k1x, k1y, k1px, k1py = deriv(s.x, s.y, s.px, s.py) 528 | k2x, k2y, k2px, k2py = deriv( 529 | s.x + k1x * dt / 2, 530 | s.y + k1y * dt / 2, 531 | s.px + k1px * dt / 2, 532 | s.py + k1py * dt / 2, 533 | ) 534 | k3x, k3y, k3px, k3py = deriv( 535 | s.x + k2x * dt / 2, 536 | s.y + k2y * dt / 2, 537 | s.px + k2px * dt / 2, 538 | s.py + k2py * dt / 2, 539 | ) 540 | k4x, k4y, k4px, k4py = deriv( 541 | s.x + k3x * dt, s.y + k3y * dt, s.px + k3px * dt, s.py + k3py * dt 542 | ) 543 | x_new = s.x + (k1x + 2 * k2x + 2 * k3x + k4x) * dt / 6 544 | y_new = s.y + (k1y + 2 * k2y + 2 * k3y + k4y) * dt / 6 545 | px_new = s.px + (k1px + 2 * k2px + 2 * k3px + k4px) * dt / 6 546 | py_new = s.py + (k1py + 2 * k2py + 2 * k3py + k4py) * dt / 6 547 | w = s.clone() 548 | w.x, w.y, w.px, w.py = x_new, y_new, px_new, py_new 549 | w.clock += 1 550 | return [w] 551 | 552 | 553 | class SymplecticEuler2DTask(Task): 554 | def __init__(self): 555 | super().__init__( 556 | "symp_euler2d", 557 | Attribute("dynamic2d"), 558 | [(Attribute("dynamic2d"), 0, 0)], 559 | clock_inc=1, 560 | ) 561 | 562 | def apply(self, s: ContinuousSubstrate2D) -> List[Substrate]: 563 | if not self.possible(s): 564 | return [] 565 | dt = s.dt 566 | fx = -finite_diff_x(s.potential2d, s.x, s.y, dt) 567 | fy = -finite_diff_y(s.potential2d, s.x, s.y, dt) 568 | px_new = s.px + fx * dt 569 | py_new = s.py + fy * dt 570 | x_new = s.x + (px_new / s.mass) * dt 571 | y_new = s.y + (py_new / s.mass) * dt 572 | w = s.clone() 573 | w.px, w.py, w.x, w.y = px_new, py_new, x_new, y_new 574 | w.clock += 1 575 | return [w] 576 | 577 | 578 | # ── 9. Quantum-Gravity Constructors ───────────────────────────────────── 579 | 580 | 581 | class GravitonEmissionTask(Task): 582 | def __init__(self, mass_attr: Attribute, emission_energy: float = 1.0): 583 | super().__init__( 584 | "emit_graviton", 585 | mass_attr, 586 | [(mass_attr, -emission_energy, 0), (GRAVITON, 0, 0)], 587 | quantum=True, 588 | irreversible=True, 589 | clock_inc=1, 590 | action_cost=emission_energy, 591 | ) 592 | 593 | 594 | class GravitonAbsorptionTask(Task): 595 | def __init__(self, mass_attr: Attribute, absorption_energy: float = 1.0): 596 | super().__init__( 597 | "absorb_graviton", 598 | GRAVITON, 599 | [(mass_attr, absorption_energy, 0)], 600 | quantum=True, 601 | irreversible=False, 602 | clock_inc=1, 603 | action_cost=absorption_energy, 604 | ) 605 | 606 | 607 | class QuantumGravityConstructor(Constructor): 608 | def __init__(self, mass_attr: Attribute, ΔE: float = 1.0): 609 | emit = GravitonEmissionTask(mass_attr, emission_energy=ΔE) 610 | absorb = GravitonAbsorptionTask(mass_attr, absorption_energy=ΔE) 611 | super().__init__([emit, absorb]) 612 | 613 | 614 | # ── 10. Electromagnetism & Coulomb coupling ────────────────────────────── 615 | 616 | 617 | class PhotonEmissionTask(Task): 618 | def __init__( 619 | self, 620 | source_attr: Attribute, 621 | emission_energy: float = 1.0, 622 | carry_residual: bool = False 623 | ): 624 | """ 625 | emission_energy > 0: amount lost by emitter. 626 | carry_residual: if True, photon.energy = pre-emission energy; 627 | else photon.energy = orig.energy + (–emission_energy). 628 | """ 629 | super().__init__( 630 | "emit_photon", source_attr, 631 | [(source_attr, -emission_energy, 0), (PHOTON, 0, 0)], 632 | quantum=True, irreversible=True, clock_inc=1, action_cost=emission_energy 633 | ) 634 | self.carry_residual = carry_residual 635 | 636 | def apply(self, s: Substrate) -> List[Substrate]: 637 | worlds = super().apply(s) 638 | for w in worlds: 639 | if w.attr is PHOTON: 640 | if self.carry_residual: 641 | # carry the emitter’s pre-emission energy 642 | w.energy = s.energy 643 | else: 644 | # residual = orig.energy + (–emission_energy) 645 | w.energy = s.energy + self.outputs[0][1] 646 | return worlds 647 | class PhotonAbsorptionTask(Task): 648 | def __init__(self, target_attr: Attribute, absorption_energy: float = 1.0): 649 | super().__init__( 650 | "absorb_photon", 651 | PHOTON, 652 | [(target_attr, absorption_energy, 0)], 653 | quantum=True, 654 | irreversible=False, 655 | clock_inc=1, 656 | action_cost=absorption_energy, 657 | ) 658 | 659 | 660 | class EMConstructor(Constructor): 661 | def __init__(self, attr: Attribute, ΔE: float = 1.0): 662 | emit = PhotonEmissionTask(attr, emission_energy=ΔE) 663 | absorb = PhotonAbsorptionTask(attr, absorption_energy=ΔE) 664 | super().__init__([emit, absorb]) 665 | 666 | 667 | def coulomb_coupling_fn(subs: List[Substrate]) -> List[List[Substrate]]: 668 | s1, s2 = subs 669 | k = 8.9875517923e9 670 | dx = s2.x - s1.x 671 | r2 = dx * dx or 1e-12 672 | F = k * s1.charge * s2.charge / r2 673 | dir12 = 1 if dx >= 0 else -1 674 | s1n, s2n = s1.clone(), s2.clone() 675 | dt = s1.dt 676 | s1n.p += F * dir12 * dt 677 | s2n.p -= F * dir12 * dt 678 | s1n.clock += 1 679 | s2n.clock += 1 680 | return [[s1n, s2n]] 681 | 682 | 683 | # ── 11. Lorentz‐Force Coupling (2D) ───────────────────────────────────── 684 | 685 | 686 | class FieldSubstrate(Substrate): 687 | def __init__(self, name: str, Bz: float): 688 | super().__init__(name, Attribute("B_field"), energy=0.0) 689 | self.Bz = Bz 690 | 691 | def clone(self) -> "FieldSubstrate": 692 | w = FieldSubstrate(self.name, self.Bz) 693 | w.energy, w.charge, w.clock = self.energy, self.charge, self.clock 694 | w.velocity, w.grav = self.velocity, self.grav 695 | w.fungible_id, w.entangled_with = self.fungible_id, self.entangled_with 696 | w._locked = self._locked 697 | return w 698 | 699 | 700 | def lorentz_coupling_fn(subs: List[Substrate]) -> List[List[Substrate]]: 701 | particle, field = subs 702 | Bz = field.Bz 703 | dt = particle.dt 704 | Fx = particle.charge * particle.py * Bz 705 | Fy = -particle.charge * particle.px * Bz 706 | p_new = particle.clone() 707 | f_new = field.clone() 708 | p_new.px += Fx * dt 709 | p_new.py += Fy * dt 710 | p_new.clock += 1 711 | f_new.clock += 1 712 | return [[p_new, f_new]] 713 | 714 | 715 | # ── 12. Task Ontology Backend System ──────────────────────────────────── 716 | 717 | from abc import ABC, abstractmethod 718 | 719 | 720 | class TaskOntologyBackend(ABC): 721 | """ 722 | Base class for pluggable task ontology backends. 723 | Each backend provides a specific domain of physics tasks. 724 | """ 725 | 726 | @abstractmethod 727 | def get_tasks(self) -> List[Task]: 728 | """Return the list of tasks provided by this backend.""" 729 | pass 730 | 731 | @abstractmethod 732 | def get_name(self) -> str: 733 | """Return the name of this backend.""" 734 | pass 735 | 736 | def get_description(self) -> str: 737 | """Return a description of this backend.""" 738 | return f"{self.get_name()} task ontology backend" 739 | 740 | 741 | class BackendRegistry: 742 | """Registry for managing task ontology backends.""" 743 | 744 | def __init__(self): 745 | self._backends: Dict[str, TaskOntologyBackend] = {} 746 | 747 | def register(self, backend: TaskOntologyBackend): 748 | """Register a backend with the registry.""" 749 | self._backends[backend.get_name()] = backend 750 | 751 | def get_backend(self, name: str) -> TaskOntologyBackend: 752 | """Get a backend by name.""" 753 | if name not in self._backends: 754 | raise ValueError(f"Backend '{name}' not found") 755 | return self._backends[name] 756 | 757 | def list_backends(self) -> List[str]: 758 | """List all registered backend names.""" 759 | return list(self._backends.keys()) 760 | 761 | def get_all_tasks(self, backend_names: Optional[List[str]] = None) -> List[Task]: 762 | """Get all tasks from specified backends (or all if none specified).""" 763 | if backend_names is None: 764 | backend_names = self.list_backends() 765 | 766 | tasks = [] 767 | for name in backend_names: 768 | backend = self.get_backend(name) 769 | tasks.extend(backend.get_tasks()) 770 | return tasks 771 | 772 | 773 | # Global backend registry 774 | _global_registry = BackendRegistry() 775 | 776 | 777 | def get_global_registry() -> BackendRegistry: 778 | """Get the global backend registry.""" 779 | return _global_registry 780 | 781 | 782 | # ── 13. Universal Constructor ──────────────────────────────────────────── 783 | 784 | 785 | class UniversalConstructor: 786 | """ 787 | Builds a new Constructor from a list of Task objects at runtime. 788 | Can work with individual tasks or task ontology backends. 789 | """ 790 | 791 | def __init__(self, backend_registry: Optional[BackendRegistry] = None): 792 | self.registry = backend_registry or get_global_registry() 793 | 794 | def build(self, program: List[Task]) -> Constructor: 795 | """Build a Constructor from a list of Task objects.""" 796 | return Constructor(program) 797 | 798 | def build_from_backends(self, backend_names: List[str]) -> Constructor: 799 | """Build a Constructor using tasks from specified backends.""" 800 | tasks = self.registry.get_all_tasks(backend_names) 801 | return Constructor(tasks) 802 | 803 | def build_with_backend(self, backend: TaskOntologyBackend) -> Constructor: 804 | """Build a Constructor using tasks from a single backend.""" 805 | return Constructor(backend.get_tasks()) 806 | 807 | # ── 14. Hydrogen Atom Constructors ───────────────────────────────────── 808 | 809 | # Hydrogen attributes 810 | HYDROGEN_GROUND = Attribute("H_ground") 811 | HYDROGEN_EXCITED = Attribute("H_excited") 812 | HYDROGEN_MOLECULE = Attribute("H2") 813 | 814 | class HydrogenExcitationTask(Task): 815 | def __init__(self, energy_gap: float = 10.2): 816 | super().__init__( 817 | "H_excite", 818 | HYDROGEN_GROUND, 819 | [(HYDROGEN_EXCITED, energy_gap, 0)], 820 | clock_inc=1, 821 | ) 822 | 823 | class HydrogenDeexcitationTask(Task): 824 | def __init__(self, energy_gap: float = 10.2): 825 | super().__init__( 826 | "H_deexcite", 827 | HYDROGEN_EXCITED, 828 | [(HYDROGEN_GROUND, -energy_gap, 0), (PHOTON, 0, 0)], 829 | quantum=True, 830 | irreversible=True, 831 | clock_inc=1, 832 | ) 833 | self.energy_gap = energy_gap 834 | 835 | def apply(self, s: Substrate) -> List[Substrate]: 836 | if not self.possible(s): 837 | return [] 838 | time.sleep(min(s.adjusted_duration(self.duration), 0.004)) 839 | orig = s.clone() 840 | worlds: List[Substrate] = [] 841 | 842 | h = orig.clone() 843 | h.attr = HYDROGEN_GROUND 844 | h.energy = orig.energy - self.energy_gap 845 | h.clock += self.clock_inc 846 | h._locked = True 847 | worlds.append(h) 848 | 849 | photon = orig.clone() 850 | photon.attr = PHOTON 851 | photon.energy = self.energy_gap 852 | photon.clock += self.clock_inc 853 | photon._locked = True 854 | worlds.append(photon) 855 | return worlds 856 | 857 | 858 | def hydrogen_collision_fn(subs: List[Substrate], bond_energy: float = 4.5) -> List[List[Substrate]]: 859 | h1, h2 = subs 860 | total = h1.energy + h2.energy 861 | if total >= bond_energy: 862 | h2mol = Substrate(f"{h1.name}+{h2.name}", HYDROGEN_MOLECULE, total - bond_energy) 863 | return [[h2mol]] 864 | else: 865 | return [[h1.clone(), h2.clone()]] 866 | 867 | 868 | class HydrogenCollisionTask(MultiSubstrateTask): 869 | def __init__(self, bond_energy: float = 4.5): 870 | fn = lambda subs: hydrogen_collision_fn(subs, bond_energy) 871 | super().__init__("H_collision", [HYDROGEN_GROUND, HYDROGEN_GROUND], fn) 872 | 873 | 874 | class HydrogenAtomConstructor(Constructor): 875 | def __init__(self, energy_gap: float = 10.2): 876 | excite = HydrogenExcitationTask(energy_gap) 877 | deexcite = HydrogenDeexcitationTask(energy_gap) 878 | super().__init__([excite, deexcite]) 879 | 880 | 881 | class HydrogenInteractionConstructor(MultiConstructor): 882 | def __init__(self, bond_energy: float = 4.5): 883 | task = HydrogenCollisionTask(bond_energy) 884 | super().__init__([task]) 885 | 886 | 887 | # ── 15. Built-in Task Ontology Backends ────────────────────────────────── 888 | 889 | 890 | class ElectromagnetismBackend(TaskOntologyBackend): 891 | """Backend providing electromagnetic tasks (photon emission/absorption).""" 892 | 893 | def __init__(self, charge_attr: Attribute, energy: float = 1.0): 894 | self.charge_attr = charge_attr 895 | self.energy = energy 896 | 897 | def get_tasks(self) -> List[Task]: 898 | return [ 899 | PhotonEmissionTask(self.charge_attr, emission_energy=self.energy), 900 | PhotonAbsorptionTask(self.charge_attr, absorption_energy=self.energy), 901 | ] 902 | 903 | def get_name(self) -> str: 904 | return "electromagnetism" 905 | 906 | 907 | class QuantumGravityBackend(TaskOntologyBackend): 908 | """Backend providing quantum gravity tasks (graviton emission/absorption).""" 909 | 910 | def __init__(self, mass_attr: Attribute, energy: float = 1.0): 911 | self.mass_attr = mass_attr 912 | self.energy = energy 913 | 914 | def get_tasks(self) -> List[Task]: 915 | return [ 916 | GravitonEmissionTask(self.mass_attr, emission_energy=self.energy), 917 | GravitonAbsorptionTask(self.mass_attr, absorption_energy=self.energy), 918 | ] 919 | 920 | def get_name(self) -> str: 921 | return "quantum_gravity" 922 | 923 | 924 | class HydrogenBackend(TaskOntologyBackend): 925 | """Backend providing hydrogen atom tasks (excitation/deexcitation).""" 926 | 927 | def __init__(self, energy_gap: float = 10.2): 928 | self.energy_gap = energy_gap 929 | 930 | def get_tasks(self) -> List[Task]: 931 | return [ 932 | HydrogenExcitationTask(energy_gap=self.energy_gap), 933 | HydrogenDeexcitationTask(energy_gap=self.energy_gap), 934 | ] 935 | 936 | def get_name(self) -> str: 937 | return "hydrogen_atoms" 938 | 939 | 940 | class ContinuousDynamicsBackend(TaskOntologyBackend): 941 | """Backend providing continuous dynamics tasks (integrators).""" 942 | 943 | def get_tasks(self) -> List[Task]: 944 | return [ 945 | DynamicsTask(), 946 | RK4Task(), 947 | SymplecticEulerTask(), 948 | Dynamics2DTask(), 949 | RK42DTask(), 950 | SymplecticEuler2DTask(), 951 | ] 952 | 953 | def get_name(self) -> str: 954 | return "continuous_dynamics" 955 | 956 | 957 | # Auto-register built-in backends with default parameters 958 | def _register_default_backends(): 959 | """Register default backends with the global registry.""" 960 | registry = get_global_registry() 961 | 962 | # Default attribute types for standard backends 963 | CHARGE = Attribute("charge_site") 964 | MASS = Attribute("mass") 965 | 966 | registry.register(ElectromagnetismBackend(CHARGE, energy=5.0)) 967 | registry.register(QuantumGravityBackend(MASS, energy=3.0)) 968 | registry.register(HydrogenBackend(energy_gap=10.2)) 969 | registry.register(ContinuousDynamicsBackend()) 970 | 971 | 972 | # Register default backends on module import 973 | _register_default_backends() 974 | --------------------------------------------------------------------------------