#!/usr/bin/env python3 """Fix the 3 op-amp notebooks that failed due to missing LM741 include. Replaces .include LM741.MOD with an inline behavioral op-amp subcircuit. """ import json import sys import time import urllib.request import urllib.error API_BASE = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8099" # Simple behavioral op-amp subcircuit (replaces LM741 include + XU1 instantiation) # Uses voltage-controlled source with high gain, rail limiting, and finite bandwidth. OPAMP_SUBCKT = ( "* Simple op-amp subcircuit\n" ".subckt OPAMP inp inn vcc vee out\n" "Rin inp inn 2MEG\n" "Egain mid 0 inp inn 200k\n" "Rout mid out 75\n" "* Output clamping to rails\n" "Dhi out vcc DCLAMP\n" "Dlo vee out DCLAMP\n" ".model DCLAMP D(IS=1e-14 N=0.1)\n" ".ends OPAMP\n" ) FIXES = { "inverting-op-amp": { "cell_id": "cell-ac", "source": ( "Inverting Op-Amp Amplifier\n" "* Dual power supply\n" "VCC vcc 0 DC 12\n" "VEE vee 0 DC -12\n" "* Input signal (1kHz sine, 100mV amplitude)\n" "Vin in 0 SIN(0 0.1 1k)\n" "* Input and feedback resistors\n" "R1 in inv 10k\n" "Rf out inv 47k\n" f"{OPAMP_SUBCKT}" "XU1 0 inv vcc vee out OPAMP\n" ".tran 10u 5m\n" ".end" ), }, "op-amp-comparator": { "cell_id": "cell-comp", "source": ( "Op-Amp Voltage Comparator\n" "* Dual power supply\n" "VCC vcc 0 DC 12\n" "VEE vee 0 DC -12\n" "* Slowly rising input (ramp)\n" "Vin inp 0 PULSE(-5 5 0 10m 10m 1n 20m)\n" "* Reference voltage (voltage divider from positive rail)\n" "R1 vcc ref 10k\n" "R2 ref 0 10k\n" f"{OPAMP_SUBCKT}" "* Op-amp in open-loop (comparator mode)\n" "XU1 inp ref vcc vee out OPAMP\n" ".tran 10u 40m\n" ".end" ), }, "photodiode-amplifier": { "cell_id": "cell-tia", "source": ( "Photodiode Transimpedance Amplifier\n" "* Dual supply\n" "VCC vcc 0 DC 12\n" "VEE vee 0 DC -12\n" "* Photodiode modeled as current source (light pulses)\n" "Iphoto 0 inv PULSE(0 1u 1m 0.1m 0.1m 2m 5m)\n" "* Feedback network\n" "Rf out inv 1MEG\n" "Cf out inv 1p\n" f"{OPAMP_SUBCKT}" "XU1 0 inv vcc vee out OPAMP\n" ".tran 10u 15m\n" ".end" ), }, } def api_put(path: str, data: dict) -> dict: url = f"{API_BASE}{path}" body = json.dumps(data).encode() req = urllib.request.Request( url, data=body, method="PUT", headers={"Content-Type": "application/json"}, ) try: with urllib.request.urlopen(req) as resp: return json.loads(resp.read()) except urllib.error.HTTPError as e: err_body = e.read().decode() if e.fp else "" print(f" ERROR {e.code}: {err_body}", file=sys.stderr) return {} def api_post(path: str, data: dict = None) -> dict: url = f"{API_BASE}{path}" body = json.dumps(data).encode() if data else b"{}" req = urllib.request.Request( url, data=body, method="POST", headers={"Content-Type": "application/json"}, ) try: with urllib.request.urlopen(req) as resp: return json.loads(resp.read()) except urllib.error.HTTPError as e: err_body = e.read().decode() if e.fp else "" print(f" ERROR {e.code}: {err_body}", file=sys.stderr) return {} def main(): print(f"Fixing {len(FIXES)} op-amp notebooks via {API_BASE}") print() for nb_id, fix in FIXES.items(): cell_id = fix["cell_id"] print(f"--- {nb_id} / {cell_id} ---") # Update the cell source result = api_put( f"/api/notebooks/{nb_id}/cells/{cell_id}", {"source": fix["source"]}, ) if result: print(f" Updated cell source") else: print(f" FAILED to update cell") continue # Regenerate schematic print(f" Generating schematic...") api_post(f"/api/notebooks/{nb_id}/cells/{cell_id}/schematic") # Re-run simulation print(f" Running simulation...") sim_result = api_post(f"/api/notebooks/{nb_id}/cells/{cell_id}/run") if sim_result and sim_result.get("success"): elapsed = sim_result.get("elapsed_seconds", 0) print(f" Simulation OK ({elapsed:.3f}s)") else: error = sim_result.get("error", "unknown") if sim_result else "no response" print(f" Simulation issue: {error[:200]}") time.sleep(0.5) print() print("Done!") if __name__ == "__main__": main()