# absolute_clock_ode.py
# Differential equation for an absolute-time clock from two parallel
# CPU threads running a known compute workload.
#
# Theory (from the framework):
#
#   dA/dt = dC̄/dt − λ · δ(t) / [ε² + δ(t)²]
#
# where
#   C₁(t), C₂(t)    = system CPU clock readings from threads 1 and 2
#   C̄(t)            = (C₁ + C₂) / 2
#   δ(t)             = C₁ − C₂
#   T_ref            = calibrated wall-time of one unit of known compute
#   λ                = drift-correction gain ∈ (0,1)
#   ε                = Laurent regulariser (≈1e-3) preventing division-by-zero

import multiprocessing as mp
import time
import math
import os

# ───── calibration constants ─────
UNIT_TIME = 0.500              # target real time per unit of compute (s)
LAMBDA    = 0.40              # drift-correction gain
EPSILON   = 1e-3              # Laurent regulariser (s)

# ───── known compute workload ─────
def known_compute(unit_index: int) -> float:
    """Deterministic CPU workload whose TRUE wall-clock cost is approximately UNIT_TIME"""
    acc = 0.0
    for i in range(4_000_000):
        acc += math.sin(i * 0.001 + unit_index) * math.cos(i * 0.0007)
        acc -= math.sqrt(i + 1.0) * 0.000001
    return acc

# ───── a thread worker ─────
def thread_worker(tid: int, q: mp.Queue, stop_flag) -> None:
    while not stop_flag.value:
        t0 = time.perf_counter()
        known_compute(tid)
        t1 = time.perf_counter()
        q.put((tid, t0, t1))

# ───── the absolute-clock observer ─────
def absolute_clock_observer(q, stop_flag, lam=LAMBDA, epsilon=EPSILON):
    log = open("abs_clock_log.csv", "w")
    log.write("step,wall_t,C1,C2,delta,C_bar,correction,A\n")
    
    A, step, last_C_bar = 0.0, 0, None
    pending = {0: None, 1: None}
    
    print("\n=== absolute_clock_observer running ===")
    print(f"  λ = {lam}    ε = {epsilon}    T_ref (pole) = {UNIT_TIME:.3f} s\n")
    print(f"{'step':>4} | {'C1':>8} | {'C2':>8} | {'δ':>10} | {'C̄':>8} | {'corr':>11} | {'A':>10}")
    print("-" * 78)
    
    while not stop_flag.value:
        try:
            tid, _t0, t1 = q.get(timeout=0.01)
            pending[tid] = (t1 - _t0,)
        except Exception:
            continue
        
        if pending[0] is None or pending[1] is None:
            continue
        
        C1, = pending[0]
        C2, = pending[1]
        pending = {0: None, 1: None}
        
        delta = C1 - C2
        C_bar = 0.5 * (C1 + C2)
        
        if last_C_bar is None:
            dC_bar = 0.0
        else:
            dC_bar = C_bar - last_C_bar
        
        correction = lam * delta / (epsilon * epsilon + delta * delta)
        
        A = A + dC_bar - correction
        last_C_bar = C_bar
        step += 1
        
        now = time.perf_counter()
        print(f"{step:>4d} | {C1:>8.4f} | {C2:>8.4f} | "
              f"{delta:>+10.5f} | {C_bar:>8.4f} | "
              f"{correction:>+11.6f} | {A:>10.6f}")
        log.write(f"{step},{now:.6f},{C1:.6f},{C2:.6f},"
                  f"{delta:.6f},{C_bar:.6f},{correction:.6f},{A:.6f}\n")
        log.flush()
    
    log.close()

# ───── main entry ─────
if __name__ == "__main__":
    stop_flag = mp.Value("b", False)
    q = mp.Queue(maxsize=4096)
    
    p0 = mp.Process(target=thread_worker, args=(0, q, stop_flag))
    p1 = mp.Process(target=thread_worker, args=(1, q, stop_flag))
    p0.start(); p1.start()
    
    try:
        absolute_clock_observer(q, stop_flag)
    except KeyboardInterrupt:
        print("\n[ctrl-c] stopping…")
    finally:
        stop_flag.value = True
        p0.join(timeout=2); p1.join(timeout=2)
        if p0.is_alive(): p0.terminate()
        if p1.is_alive(): p1.terminate()
        print("done.")