app
Sample Experiment: RC Low-Pass Filter Response
Public Made by Adomby adom
Working sample of Adom's experiment publication format: JSONL data, sidecar metadata, provenance, and an AI reproduction skill. Prepared for NSF PCL FAIR-data due diligence.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
#!/usr/bin/env python3
"""Sample Experiment: frequency response of a first-order RC low-pass filter.
Computes |H(f)| and phase for H(f) = 1 / (1 + j*2*pi*f*R*C) over 10 Hz - 1 MHz,
writes the dataset as JSONL plus a sidecar metadata JSON, and renders a Bode plot.
Fully deterministic: re-running this script reproduces the dataset bit-for-bit.
"""
import json, math, hashlib, datetime, pathlib
R_OHMS = 1000.0 # 1 kOhm (design ref: Yageo RC0603FR-071KL)
C_FARADS = 100e-9 # 100 nF (design ref: Murata GRM188R71H104KA93D)
F_CUTOFF = 1.0 / (2.0 * math.pi * R_OHMS * C_FARADS) # ~1591.55 Hz
POINTS_PER_DECADE = 20
F_START_EXP, F_STOP_EXP = 1, 6 # 10 Hz .. 1 MHz
root = pathlib.Path(__file__).resolve().parent.parent
data_path = root / "data" / "rc_lowpass_response.jsonl"
rows = []
n = (F_STOP_EXP - F_START_EXP) * POINTS_PER_DECADE + 1
for i in range(n):
f = 10 ** (F_START_EXP + i / POINTS_PER_DECADE)
w_rc = 2.0 * math.pi * f * R_OHMS * C_FARADS
gain = 1.0 / math.sqrt(1.0 + w_rc * w_rc)
rows.append({
"point": i,
"frequency_hz": round(f, 6),
"gain_linear": round(gain, 9),
"gain_db": round(20.0 * math.log10(gain), 6),
"phase_deg": round(-math.degrees(math.atan(w_rc)), 6),
})
with open(data_path, "w") as fh:
for r in rows:
fh.write(json.dumps(r) + "\n")
sha = hashlib.sha256(data_path.read_bytes()).hexdigest()
sidecar = {
"dataset": "rc_lowpass_response.jsonl",
"title": "RC low-pass filter frequency response (computed)",
"description": "Magnitude and phase of a first-order RC low-pass filter (R=1 kOhm, C=100 nF, fc~=1591.5 Hz), 20 points/decade from 10 Hz to 1 MHz.",
"created_utc": "2026-07-10T00:00:00Z",
"generator": {"script": "analysis/run_experiment.py", "runtime": "python3", "deterministic": True},
"method": "closed-form transfer function H(f) = 1/(1 + j*2*pi*f*R*C); no physical measurement",
"parameters": {"R_ohms": R_OHMS, "C_farads": C_FARADS, "f_cutoff_hz": round(F_CUTOFF, 3),
"points_per_decade": POINTS_PER_DECADE, "f_range_hz": [10, 1000000]},
"schema": {
"format": "jsonl",
"fields": [
{"name": "point", "type": "int", "unit": None, "description": "0-based sample index"},
{"name": "frequency_hz", "type": "float", "unit": "Hz", "description": "stimulus frequency"},
{"name": "gain_linear", "type": "float", "unit": "V/V", "description": "|H(f)|"},
{"name": "gain_db", "type": "float", "unit": "dB", "description": "20*log10(|H(f)|)"},
{"name": "phase_deg", "type": "float", "unit": "degrees", "description": "arg(H(f)), negative = lag"},
],
},
"rows": len(rows),
"sha256": sha,
"license": "CC-BY-4.0",
}
(root / "data" / "rc_lowpass_response.sidecar.json").write_text(json.dumps(sidecar, indent=2) + "\n")
print(f"wrote {len(rows)} rows, fc={F_CUTOFF:.2f} Hz, sha256={sha[:16]}...")
# Bode plot
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
freqs = [r["frequency_hz"] for r in rows]
gains = [r["gain_db"] for r in rows]
phases = [r["phase_deg"] for r in rows]
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6.5), sharex=True)
ax1.semilogx(freqs, gains, color="#1a4480", lw=2)
ax1.axvline(F_CUTOFF, color="#c05621", ls="--", lw=1, label=f"fc = {F_CUTOFF:.1f} Hz (-3 dB)")
ax1.axhline(-3.0103, color="#c05621", ls=":", lw=1)
ax1.set_ylabel("Gain (dB)"); ax1.grid(True, which="both", alpha=0.3); ax1.legend()
ax1.set_title("RC low-pass frequency response - R=1 k$\\Omega$, C=100 nF")
ax2.semilogx(freqs, phases, color="#1a4480", lw=2)
ax2.axvline(F_CUTOFF, color="#c05621", ls="--", lw=1)
ax2.set_ylabel("Phase (deg)"); ax2.set_xlabel("Frequency (Hz)"); ax2.grid(True, which="both", alpha=0.3)
fig.tight_layout()
fig.savefig(root / "docs" / "bode-plot.png", dpi=110)
print("plot saved")