#!/usr/bin/env python3
"""FlashMemory synthetic Id-Vg teaching exercise. Python 3 standard library only.
Run next to mosfet-iv.csv: python3 analyze.py. No uploads or network calls.
"""
import csv
import json
import math
from collections import defaultdict
from pathlib import Path

PATH = Path(__file__).with_name("mosfet-iv.csv")
TARGET_A = 1e-7
series = defaultdict(list)
with PATH.open(encoding="utf-8", newline="") as f:
    for row in csv.DictReader(f):
        group = row["group"]
        vd = float(row["vds_v"])
        vg = float(row["vgs_v"])
        current = float(row["id_a"])
        temp = float(row["temp_c"])
        if group not in {"Reference", "Comparison"} or vd not in {0.05, 0.70}:
            raise ValueError("Unexpected device group or Vds condition")
        if current <= 0 or not math.isfinite(current) or temp != 25:
            raise ValueError("Current must be positive and temperature fixed")
        series[group, vd].append((vg, current))

def extract_vth(points):
    """Constant-current Vth, interpolate Vgs against log10(Id)."""
    for (vg0, id0), (vg1, id1) in zip(points, points[1:]):
        if id0 <= TARGET_A <= id1:
            y0, y1, target = math.log10(id0), math.log10(id1), math.log10(TARGET_A)
            return vg0 + (vg1-vg0)*(target-y0)/(y1-y0)
    raise ValueError("Target current not bracketed in this sweep")

def extract_ss(points):
    """mV/dec from linear least squares of log10(Id) versus Vgs in a declared window."""
    selected = [(vg, math.log10(current)) for vg, current in points if 1e-10 <= current <= 1e-8]
    if len(selected) < 3:
        raise ValueError("At least three subthreshold points are required")
    xmean = sum(vg for vg, _ in selected)/len(selected)
    ymean = sum(y for _, y in selected)/len(selected)
    slope = sum((x-xmean)*(y-ymean) for x, y in selected)/sum((x-xmean)**2 for x, _ in selected)
    if slope <= 0:
        raise ValueError("Unexpected subthreshold slope")
    return 1000/slope, len(selected)

results = {}
for (group, vd), points in sorted(series.items()):
    points.sort()
    if len(points) != 36 or len({x for x, _ in points}) != 36:
        raise ValueError("Each synthetic sweep must contain 36 unique Vgs values")
    if abs(points[0][0]) > 1e-12:
        raise ValueError("Ioff reference Vgs=0 is missing")
    vth = extract_vth(points)
    ss, samples = extract_ss(points)
    results[group, f"{vd:.2f}"] = {
        "vth_v_at_1e-7_a": round(vth, 4),
        "ss_mV_per_dec_in_1e-10_to_1e-8_a": round(ss, 2),
        "ss_point_count": samples,
        "id_at_vgs_0_a": f"{points[0][1]:.3e}",
    }

summary = {"dataset": "SYNTHETIC_TRAINING_ONLY", "definition": "constant-current Vth at Id=1e-7 A, two drain biases; SS extracted in fixed current window", "results": {}}
for group in ("Reference", "Comparison"):
    low = results[group, "0.05"]
    high = results[group, "0.70"]
    summary["results"][group] = {
        "vth_low_v": low["vth_v_at_1e-7_a"],
        "vth_high_v": high["vth_v_at_1e-7_a"],
        "dibl_mV_per_v": round(1000*(low["vth_v_at_1e-7_a"]-high["vth_v_at_1e-7_a"])/(0.70-0.05), 2),
        "subthreshold_swing_low_vd_mV_per_dec": low["ss_mV_per_dec_in_1e-10_to_1e-8_a"],
        "id_at_vgs_0_low_vd_a": low["id_at_vgs_0_a"],
    }
assert abs(summary["results"]["Reference"]["vth_low_v"]-0.40)<0.00001
assert abs(summary["results"]["Comparison"]["vth_low_v"]-0.36)<0.00001
assert abs(summary["results"]["Reference"]["dibl_mV_per_v"]-92.31)<0.01
assert abs(summary["results"]["Comparison"]["dibl_mV_per_v"]-153.85)<0.01
assert abs(summary["results"]["Reference"]["subthreshold_swing_low_vd_mV_per_dec"]-90)<0.01
assert abs(summary["results"]["Comparison"]["subthreshold_swing_low_vd_mV_per_dec"]-110)<0.01
print(json.dumps(summary, ensure_ascii=False, indent=2))
