- scripts/create-spicebook-notebooks.py: Creates 8 circuit notebooks (555 timer, op-amp, comms, sensor) via SpiceBook REST API with full SPICE netlists and educational markdown - scripts/fix-opamp-notebooks.py: Patches 3 op-amp notebooks that fail due to missing LM741.MOD by inlining a behavioral op-amp subcircuit - docs/agent-threads/spicebook-embed-bugfixes/: 3-message coordination thread documenting the 4 embed bugs (postMessage type, waveform CSS vars, theme remount, light-mode overrides) and their verification
162 lines
4.8 KiB
Python
162 lines
4.8 KiB
Python
#!/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()
|