185 lines
7.4 KiB
Python
185 lines
7.4 KiB
Python
"""fractonaut_calibrate.py - regression test for Fractonaut.
|
|
|
|
Run AFTER restarting Fractonaut with the new prompt. Drives the lattice
|
|
through 4 known states, declares the injection state to Fractonaut, asks
|
|
for an observation, grades it for forbidden/required patterns.
|
|
"""
|
|
import zmq, json, time, urllib.request, sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
CMD_PORT = 5557
|
|
FRACTO_API = "http://127.0.0.1:28822"
|
|
DWELL_BASELINE = 90
|
|
DWELL_STATE = 240
|
|
INJ_INTERVAL = 60
|
|
LEFT_X, RIGHT_X, CENTER_Y, SIGMA = 400, 624, 512, 48
|
|
|
|
OUT_DIR = Path(f"/mnt/d/Resonance_Engine/traj/calibrate_{datetime.now().strftime('%Y%m%dT%H%M%S')}")
|
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
OUT_FILE = OUT_DIR / "calibration.jsonl"
|
|
|
|
ctx = zmq.Context()
|
|
cmd = ctx.socket(zmq.PUB)
|
|
cmd.connect(f"tcp://127.0.0.1:{CMD_PORT}")
|
|
time.sleep(0.5)
|
|
|
|
def send(d):
|
|
cmd.send_string(json.dumps(d)); time.sleep(0.05)
|
|
|
|
def log(m):
|
|
print(f"[{datetime.now().strftime('%H:%M:%S')}] {m}", flush=True)
|
|
|
|
def set_inj(state):
|
|
body = json.dumps({"state": state}).encode()
|
|
req = urllib.request.Request(f"{FRACTO_API}/set_injection_state",
|
|
data=body, headers={"Content-Type":"application/json"}, method="POST")
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=5) as r:
|
|
return json.loads(r.read())
|
|
except Exception as e:
|
|
log(f" WARN set_inj({state}) failed: {e}")
|
|
return None
|
|
|
|
def ask(question, timeout=180):
|
|
body = json.dumps({"question": question}).encode()
|
|
req = urllib.request.Request(f"{FRACTO_API}/ask",
|
|
data=body, headers={"Content-Type":"application/json"}, method="POST")
|
|
t0 = time.time()
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
return json.loads(r.read()).get("response",""), time.time()-t0
|
|
except Exception as e:
|
|
return f"(error: {e})", time.time()-t0
|
|
|
|
FORBIDDEN_ALWAYS = [
|
|
"left site","right site","left half","right half",
|
|
"pushed harder","side dominant","side is dominant",
|
|
"collapse","decay","amplifying trend",
|
|
"the buy side","the sell side",
|
|
]
|
|
FORBIDDEN_QUIESCENT = ["buy","sell","market","injection","inject","pulse"]
|
|
REQUIRED_QUIESCENT = ["quiescent","baseline","within","operating range","operating point"]
|
|
REQUIRED_ACTIVE = ["inject","pulse"]
|
|
|
|
# Phrases that flip a forbidden word into a denial / negation context.
|
|
# If the forbidden word appears within ~50 chars after any of these, it's
|
|
# NOT an assertive claim and should be allowed.
|
|
DENIAL_MARKERS = [
|
|
"no ", "not ", "without ", "rather than ", "no longer ",
|
|
"absence of ", "absent", "any directional ", "any external ",
|
|
"any market ", "any inject", "any pulse", "any buy", "any sell",
|
|
]
|
|
|
|
def _is_denied(text_lower, word_pos, word):
|
|
"""Return True if the occurrence of `word` at word_pos in text_lower
|
|
appears in a denial context (preceded within ~80 chars by a denial marker)."""
|
|
window_start = max(0, word_pos - 80)
|
|
preceding = text_lower[window_start:word_pos]
|
|
for marker in DENIAL_MARKERS:
|
|
if marker in preceding:
|
|
return True
|
|
return False
|
|
|
|
def _find_assertive(text_lower, word):
|
|
"""Find the first occurrence of `word` that is NOT in a denial context.
|
|
Returns the position, or -1 if all occurrences are denied."""
|
|
start = 0
|
|
while True:
|
|
pos = text_lower.find(word, start)
|
|
if pos == -1:
|
|
return -1
|
|
if not _is_denied(text_lower, pos, word):
|
|
return pos
|
|
start = pos + 1
|
|
|
|
def grade(state, response):
|
|
r = response.lower()
|
|
fails = []
|
|
for f in FORBIDDEN_ALWAYS:
|
|
if f in r:
|
|
pos = r.find(f)
|
|
if not _is_denied(r, pos, f):
|
|
fails.append(f"forbidden_always: '{f}'")
|
|
if state in ("BASELINE","KHRA_ELEVATED","GIXX_ELEVATED"):
|
|
for f in FORBIDDEN_QUIESCENT:
|
|
if _find_assertive(r, f) != -1:
|
|
fails.append(f"forbidden_in_quiescent (assertive): '{f}'")
|
|
if state == "BASELINE":
|
|
if not any(req in r for req in REQUIRED_QUIESCENT):
|
|
fails.append("missing_required_quiescent")
|
|
if state == "ALL_ON":
|
|
# For ALL_ON we need at least one ASSERTIVE mention of injection
|
|
if not any(_find_assertive(r, req) != -1 for req in REQUIRED_ACTIVE):
|
|
fails.append("missing_required_active (assertive)")
|
|
return ("PASS" if not fails else "FAIL"), fails
|
|
|
|
def run_state(name, khra, gixx, inject, dwell, inj_state):
|
|
log(f"=== STATE: {name} khra={khra} gixx={gixx} inject={inject} inj_state={inj_state} ===")
|
|
send({"cmd":"set_khra_amp","amp":khra})
|
|
send({"cmd":"set_gixx_amp","amp":gixx})
|
|
set_inj(inj_state)
|
|
log(f" dwell {dwell}s...")
|
|
t_start = time.time()
|
|
deadline = t_start + dwell
|
|
next_inj = t_start
|
|
while time.time() < deadline:
|
|
if inject and time.time() >= next_inj:
|
|
send({"cmd":"inject_density","x":LEFT_X,"y":CENTER_Y,"sigma":SIGMA,"strength":+0.15})
|
|
send({"cmd":"inject_density","x":RIGHT_X,"y":CENTER_Y,"sigma":SIGMA,"strength":-0.15})
|
|
log(f" injected dipole at t+{int(time.time()-t_start)}s")
|
|
next_inj = time.time() + INJ_INTERVAL
|
|
time.sleep(1)
|
|
q = (f"State {name}. Describe the substrate right now in 3-5 sentences. "
|
|
f"Only call out channels flagged with ! or !!. If nothing is flagged, "
|
|
f"say the substrate is in its quiescent operating range and stop.")
|
|
resp, took = ask(q)
|
|
verdict, fails = grade(name, resp)
|
|
log(f" [{name}] fracto ({took:.1f}s) {verdict}")
|
|
for f in fails:
|
|
log(f" FAIL: {f}")
|
|
log(f" response: {resp[:300]}{'...' if len(resp)>300 else ''}")
|
|
with open(OUT_FILE, "a") as fh:
|
|
fh.write(json.dumps({
|
|
"ts": datetime.now().isoformat(),
|
|
"state": name, "khra": khra, "gixx": gixx, "inject": inject,
|
|
"inj_state": inj_state, "verdict": verdict, "fails": fails,
|
|
"response": resp, "took_s": took,
|
|
}) + "\n")
|
|
return verdict
|
|
|
|
def main():
|
|
log("=== fractonaut_calibrate ===")
|
|
log(f"out: {OUT_DIR}")
|
|
try:
|
|
with urllib.request.urlopen(f"{FRACTO_API}/status", timeout=5) as r:
|
|
st = json.loads(r.read())
|
|
log(f"fracto OK: model={st['model']} cycle={st['cycle']} inj_state={st.get('injection_state','?')}")
|
|
except Exception as e:
|
|
log(f"FATAL: Fractonaut not reachable: {e}")
|
|
sys.exit(1)
|
|
verdicts = []
|
|
try:
|
|
verdicts.append(("BASELINE", run_state("BASELINE", 0.030, 0.008, False, DWELL_BASELINE, "INACTIVE")))
|
|
verdicts.append(("KHRA_ELEVATED", run_state("KHRA_ELEVATED", 0.050, 0.008, False, DWELL_STATE, "INACTIVE")))
|
|
verdicts.append(("GIXX_ELEVATED", run_state("GIXX_ELEVATED", 0.030, 0.024, False, DWELL_STATE, "INACTIVE")))
|
|
verdicts.append(("ALL_ON", run_state("ALL_ON", 0.050, 0.024, True, DWELL_STATE, "ACTIVE")))
|
|
finally:
|
|
log("=== RESTORE baseline ===")
|
|
send({"cmd":"set_khra_amp","amp":0.030})
|
|
send({"cmd":"set_gixx_amp","amp":0.008})
|
|
set_inj("UNKNOWN")
|
|
log(" (finally) baseline restored")
|
|
log("")
|
|
log("=== SUMMARY ===")
|
|
p = sum(1 for _,v in verdicts if v=="PASS")
|
|
f = sum(1 for _,v in verdicts if v=="FAIL")
|
|
for n,v in verdicts:
|
|
log(f" {n:<16} {v}")
|
|
log(f" TOTAL: {p} PASS / {f} FAIL")
|
|
log(f" file: {OUT_FILE}")
|
|
sys.exit(0 if f==0 else 1)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|