Single tone HB of a self-heating lighbulb
// Mirrors test_hb3.sim

embed "lightbulb.va" <<<FILE
`include "constants.vams"
`include "disciplines.vams"

module lightbulb(A, B);
    // Self-heating lightbulb
    (*desc = "Thermal resistance", units = "K/W"*) 
      parameter real Rth = 50 from [0:inf];
    (*desc = "Thermal capacitance", units = "J/K"*) 
      parameter real Cth = 10e-3 from [0:inf];
    (*desc = "Ambient temperature", units = "deg C"*) 
      parameter real Ta = 25 from [0:inf];
    (*desc = "Resistance coeff R0", units = "Ohm"*) 
      parameter real R0 = 8.612 from [0:inf];
    (*desc = "Resistance coeff p0", units = ""*) 
      parameter real p0 = 8.612 from [-inf:inf];
    (*desc = "Resistance coeff p1", units = "K^-1"*) 
      parameter real p1 = 0.0269 from [-inf:inf];
    (*desc = "Resistance coeff p1", units = "K^-2"*) 
      parameter real p2 = 1.914e-6 from [-inf:inf];
    
    // Variables
    real p, i, r;

    // Terminals and internal nodes
    inout A, B;
    electrical A, B, T;
    
    analog begin
        r = R0 * (p0 + p1*V(T) + p2*pow(V(T),2));
        i = V(A,B)/r;
        p = i*V(A,B);
        I(T) <+ -p + (V(T)-Ta)/Rth + ddt(Cth*V(T));
        I(A,B) <+ i;
    end
endmodule
>>>FILE

ground 0 

load "lightbulb.va"

model vsource vsource
model lightbulb lightbulb rth=(3000-25)/60 ta=25

v1 (1 0) vsource dc=0 type="sine" sinedc=0.0 ampl=325 freq=50
b1 (1 0) lightbulb

control
  options rawfile="binary" strictsave=1
  options pss_itl=500
  options pss_tolscale=1.5e-3

  save default

  analysis pss1 pss driven=1 tper=20m tstab=200m writestab=1

  postprocess(PYTHON, "runme.py")
endc

embed "runme.py" <<<FILE
from rawfile import rawread
from runtest import *
import numpy as np
import sys

# -------------------------------------------------------------------
# Transient results
# -------------------------------------------------------------------
plot  = rawread('pss1.tran.raw').get()
t     = plot['time']
T    = plot['b1:t']

tests = []

status = bool(np.all(np.isfinite(T)) and len(t) > 10)
tests.append(status)
print(f"Tran: simulation completed ({len(t)} points): {status}")

n_half = len(t) // 2
T_2h  = T[n_half:]
amp_pp = float(np.max(T_2h) - np.min(T_2h))
status = amp_pp > 0.1
tests.append(status)
print(f"Tran: peak-to-peak in 2nd half = {amp_pp*1e3:.1f} mV (expect > 100 mV): {status}")

ppss  = rawread('pss1.raw').get()
tp    = ppss['time']
Tp    = ppss['b1:t']
ip    = -ppss['v1:flow(br)']
T0    = float(tp[-1] - tp[0])
f0    = 1.0 / T0
Tpp   = float(Tp.max() - Tp.min())
print(f"PSS:  converged T0={T0:.4e} s  f0={f0:.1f} Hz  Tpp={Tpp*1e3:.0f} deg C")

match = np.abs(Tp[-1]-Tp[0])/np.abs(Tp).max()
ok    = match<1e-12
print("Endpoint relative delta:", match, "PASS" if ok else "")
tests.append(ok)

# ---------------------------
# Spectrum from PSS waveform
# ---------------------------
N_fft  = 1024
tp_uni = np.linspace(float(tp[0]), float(tp[0]) + T0, N_fft, endpoint=False)
Tp_uni = np.interp(tp_uni, tp, Tp)
fmag   = np.abs(np.fft.rfft(Tp_uni)) * (2.0 / N_fft)
fmag[0] /= 2                             # DC bin is one-sided already
freqs  = np.arange(len(fmag)) * f0      # harmonic frequencies in Hz

if isTest():
    sys.exit(not all(tests))

import matplotlib.pyplot as plt

n_panels = 2
fig, axes = plt.subplots(n_panels, 1, figsize=(9, 4 * n_panels), constrained_layout=True)
fig.suptitle("Single tone PSS of a self-heating lighbulb")

axes[0].plot(tp * 1e3, Tp, lw=1.2, color='C1')
axes[0].axhline(Tp[0], ls='--', alpha=0.2)
axes[0].set_xlabel("time [ms]")
axes[0].set_ylabel("T [deg C]")
axes[0].set_title(f"PSS periodic response  f0={f0:.1f} Hz")

axes[1].stem(freqs, fmag)
axes[1].set_yscale("log")
axes[1].set_xlabel("frequency [Hz]")
axes[1].set_ylabel("|FFT| amplitude [deg C]")
axes[1].set_title("PSS spectrum")
axes[1].set_xlim(-10, 510)

plt.show()
>>>FILE