Package overview

PSC / solar cell equivalent-circuit simulation package.

class psc_sim.CellParameters(I0: 'float' = 1e-08, n: 'float' = 1.5, Iph: 'float' = 0.03, Rs: 'float' = 1.0, Rsh: 'float' = 1000.0, T: 'float' = 300.0, Cj: 'float' = 1e-07, Rion: 'float' = 500.0, Cion: 'float' = 1e-06)[source]
I0: float = 1e-08
n: float = 1.5
Iph: float = 0.03
Rs: float = 1.0
Rsh: float = 1000.0
T: float = 300.0
Cj: float = 1e-07
Rion: float = 500.0
Cion: float = 1e-06
vt() float[source]
class psc_sim.CellMetrics(voc: 'float', isc: 'float', ff: 'float', pce: 'float', rs_extracted: 'float', pmax: 'float', vmp: 'float', imp: 'float')[source]
voc: float
isc: float
ff: float
pce: float
rs_extracted: float
pmax: float
vmp: float
imp: float
class psc_sim.SimulationSession(params: CellParameters, iv_backend: IVBackend = <factory>, eis_backend: EISBackend = <factory>, iv_spec: SweepSpec = SweepSpec(v_min=-0.3, v_max=1.25, step=0.01), sr_spec: SweepSpec = SweepSpec(v_min=0.0, v_max=0.9, step=0.01), rs_spec: SweepSpec = SweepSpec(v_min=0.0, v_max=0.8, step=0.02), tran_spec: TranSpec = TranSpec(t_step_s=0.0001, t_stop_s=0.01, pulse_v_low=0.0, pulse_v_high=0.5, pulse_delay_s=0.001, pulse_rise_s=0.0001, pulse_fall_s=0.0001, pulse_on_s=0.005, pulse_period_s=0.01), lt_iv_result: IVResult | None = None, lt_eis_result: tuple[~numpy.ndarray, ~numpy.ndarray, ~numpy.ndarray] | None=None, lt_tran_result: TRANResult | None = None, measured_eis: MeasuredEIS | None = None, rs_fit_mask: RsFitMask | None = None, _dispatcher_cache: SimulationDispatcher | None = None, _dispatcher_key: tuple[float, ...] | None=None)[source]

Orchestrates cell parameters, sweep specs, Python/LTspice backends, and overlays.

Typical library usage:

from psc_sim import CellParameters, SimulationSession

session = SimulationSession(CellParameters())
V, I, P = session.iv_curve()
met = session.metrics()
f, zre, zim = session.eis_default_sweep()
params: CellParameters
iv_backend: IVBackend
eis_backend: EISBackend
iv_spec: SweepSpec = SweepSpec(v_min=-0.3, v_max=1.25, step=0.01)
sr_spec: SweepSpec = SweepSpec(v_min=0.0, v_max=0.9, step=0.01)
rs_spec: SweepSpec = SweepSpec(v_min=0.0, v_max=0.8, step=0.02)
tran_spec: TranSpec = TranSpec(t_step_s=0.0001, t_stop_s=0.01, pulse_v_low=0.0, pulse_v_high=0.5, pulse_delay_s=0.001, pulse_rise_s=0.0001, pulse_fall_s=0.0001, pulse_on_s=0.005, pulse_period_s=0.01)
lt_iv_result: IVResult | None = None
lt_eis_result: tuple[ndarray, ndarray, ndarray] | None = None
lt_tran_result: TRANResult | None = None
measured_eis: MeasuredEIS | None = None
rs_fit_mask: RsFitMask | None = None
property dispatcher: SimulationDispatcher
iv_result() IVResult[source]
solve_i(V: float, rs: float | None = None, *, i_hint: float | None = None) float[source]
iv_curve(v_min: float | None = None, v_max: float | None = None, step: float | None = None, rs: float | None = None) tuple[ndarray, ndarray, ndarray][source]
extract_rs_from_sr_inv(i_mA: ndarray | None = None, sr_inv: ndarray | None = None, mask: slice | ndarray | None = None, rs: float | None = None) float[source]
metrics(pin_w_cm2: float = 0.1) CellMetrics[source]
sr_vs_bias(v_min: float | None = None, v_max: float | None = None, step: float | None = None, rs: float | None = None) tuple[ndarray, ndarray][source]
rs_extraction_series(v_min: float | None = None, v_max: float | None = None, step: float | None = None, rs: float | None = None) tuple[ndarray, ndarray][source]
rs_fit(i_mA_lo: float | None = None, i_mA_hi: float | None = None, *, positive_generation: bool | None = None) float[source]
eis_impedance(freqs: ndarray) tuple[ndarray, ndarray][source]
eis_default_sweep() tuple[ndarray, ndarray, ndarray][source]
clear_lt_results() None[source]
clear_measured_eis() None[source]
run_ltspice_iv(ltspice_exe: str | Path | None = None, workdir: str | Path | None = None, *, spec: SweepSpec | None = None) IVResult[source]
run_ltspice_eis(ltspice_exe: str | Path | None = None, workdir: str | Path | None = None) tuple[ndarray, ndarray, ndarray][source]
run_ltspice_tran(ltspice_exe: str | Path | None = None, workdir: str | Path | None = None, *, tran_spec: TranSpec | None = None) TRANResult[source]
static ltspice_available(exe: str | Path | None) bool[source]
class psc_sim.SweepSpec(v_min: 'float' = -0.3, v_max: 'float' = 1.25, step: 'float' = 0.01)[source]
v_min: float = -0.3
v_max: float = 1.25
step: float = 0.01
class psc_sim.IVResult(V: 'np.ndarray', I: 'np.ndarray', P: 'np.ndarray', truncated: 'bool' = False)[source]
V: ndarray
I: ndarray
P: ndarray
truncated: bool = False
classmethod from_arrays(V: ndarray, I: ndarray, P: ndarray) IVResult[source]
class psc_sim.TRANResult(t: ndarray, V: ndarray, I: ndarray)[source]

Transient waveform from LTspice .tran.

t: ndarray
V: ndarray
I: ndarray
psc_sim.residual_iv_implicit(I: float, V: float, p: CellParameters, rs: float) float[source]

f(I)=0 for the implicit one-diode equation.

Simulation layer

Unified access to IV and EIS simulation backends.

class psc_sim.simulation.dispatcher.SimulationDispatcher(params: 'CellParameters', iv_backend: 'IVBackend' = <factory>, eis_backend: 'EISBackend' = <factory>, iv_spec: 'SweepSpec' = SweepSpec(v_min=-0.3, v_max=1.25, step=0.01))[source]

Thin orchestrator for parameters, backends, and sweep specs.

class psc_sim.simulation.session.SimulationSession(params: CellParameters, iv_backend: IVBackend = <factory>, eis_backend: EISBackend = <factory>, iv_spec: SweepSpec = SweepSpec(v_min=-0.3, v_max=1.25, step=0.01), sr_spec: SweepSpec = SweepSpec(v_min=0.0, v_max=0.9, step=0.01), rs_spec: SweepSpec = SweepSpec(v_min=0.0, v_max=0.8, step=0.02), tran_spec: TranSpec = TranSpec(t_step_s=0.0001, t_stop_s=0.01, pulse_v_low=0.0, pulse_v_high=0.5, pulse_delay_s=0.001, pulse_rise_s=0.0001, pulse_fall_s=0.0001, pulse_on_s=0.005, pulse_period_s=0.01), lt_iv_result: IVResult | None = None, lt_eis_result: tuple[~numpy.ndarray, ~numpy.ndarray, ~numpy.ndarray] | None=None, lt_tran_result: TRANResult | None = None, measured_eis: MeasuredEIS | None = None, rs_fit_mask: RsFitMask | None = None, _dispatcher_cache: SimulationDispatcher | None = None, _dispatcher_key: tuple[float, ...] | None=None)[source]

Orchestrates cell parameters, sweep specs, Python/LTspice backends, and overlays.

Typical library usage:

from psc_sim import CellParameters, SimulationSession

session = SimulationSession(CellParameters())
V, I, P = session.iv_curve()
met = session.metrics()
f, zre, zim = session.eis_default_sweep()

IV simulation backends: Python implicit solver and optional LTspice.

class psc_sim.simulation.backends.IVBackend(*args, **kwargs)[source]
class psc_sim.simulation.backends.PythonIVBackend[source]

Default backend: implicit 1-diode generation branch.

class psc_sim.simulation.backends.LTspiceIVBackend(ltspice_exe: str | Path | None = None, workdir: str | Path | None = None)[source]

LTspice .dc sweep; point solve uses nearest sweep sample.

class psc_sim.simulation.backends.EISBackend(*args, **kwargs)[source]
class psc_sim.simulation.backends.PythonEISBackend[source]

Lumped-element EIS from eis_lumped.py.

class psc_sim.simulation.backends.LTspiceEISBackend(ltspice_exe: str | Path | None = None, workdir: str | Path | None = None)[source]

LTspice AC analysis; Z = V(v_out) / I(Vbias) from .raw.

class psc_sim.simulation.backends.LTspiceTRANBackend(ltspice_exe: str | Path | None = None, workdir: str | Path | None = None, tran_spec: TranSpec | None = None)[source]

LTspice transient analysis from .tran netlist.

Shared types for I–V sweeps and simulation results.

class psc_sim.simulation.types.SweepSpec(v_min: 'float' = -0.3, v_max: 'float' = 1.25, step: 'float' = 0.01)[source]
class psc_sim.simulation.types.IVResult(V: 'np.ndarray', I: 'np.ndarray', P: 'np.ndarray', truncated: 'bool' = False)[source]
class psc_sim.simulation.types.RsFitMask(i_mA_lo: float, i_mA_hi: float, positive_generation: bool = True)[source]

Rs linear-fit interval on the I_mA axis (generation-positive convention).

class psc_sim.simulation.types.TranSpec(t_step_s: float = 0.0001, t_stop_s: float = 0.01, pulse_v_low: float = 0.0, pulse_v_high: float = 0.5, pulse_delay_s: float = 0.001, pulse_rise_s: float = 0.0001, pulse_fall_s: float = 0.0001, pulse_on_s: float = 0.005, pulse_period_s: float = 0.01)[source]

LTspice .tran step/stop and Vbias PULSE (times in seconds; formatted with m = ms).

class psc_sim.simulation.types.MeasuredEIS(freqs_hz: ndarray, zre: ndarray, zim: ndarray)[source]

Measured EIS curve loaded from CSV (freq_hz, Re(Z), Im(Z)).

class psc_sim.simulation.types.TRANResult(t: ndarray, V: ndarray, I: ndarray)[source]

Transient waveform from LTspice .tran.

GUI plots

GUI session bridge

Sync CellParameters between Qt spin boxes and SimulationSession.

Solvers and metrics

Implicit 1-diode I–V solver (generation branch, Voc bracketing, IV sweep).

psc_sim.iv_solver.terminal_voc(p: CellParameters, rs: float | None = None, *, v_hi: float = 2.0) float | None[source]

Return the I=0 terminal voltage for the diode + shunt branch.

psc_sim.iv_solver.solve_generation_current(V: float, p: CellParameters, rs: float, i_hint: float | None = None) float[source]

Solve the physical photovoltaic branch between generated current and Voc.

psc_sim.iv_solver.solve_current(V: float, p: CellParameters, rs: float | None = None, *, i_hint: float | None = None) float[source]

Single public API for terminal current at bias V (generation branch).

psc_sim.iv_solver.sweep_iv_curve(p: CellParameters, v_min: float = -0.3, v_max: float = 1.25, step: float = 0.01, rs: float | None = None, *, spec: SweepSpec | None = None) tuple[ndarray, ndarray, ndarray][source]

Voltage sweep with Voc truncation beyond the physical open-circuit point.

psc_sim.iv_solver.sweep_iv_result(p: CellParameters, spec: SweepSpec, rs: float | None = None) IVResult[source]

Voltage sweep returning IVResult with truncation flag.

Lumped-element EIS: canonical series blocks (HTML computeEIS).

psc_sim.eis_lumped.eis_impedance(p: CellParameters, freqs: ndarray) tuple[ndarray, ndarray][source]

Return Zre, Zim (mathematical Im) for the canonical series model:

Z = Rs + Z_parallel(Rsh, Cj) + (Rion + 1/(jω Cion))

Same algebra as solar_cell_sim_app.html computeEIS.

Cell metrics, SR curves, and Rs extraction from the implicit diode model.

psc_sim.cell_metrics.normalized_sr_at_bias(v: float, i: float, p: CellParameters, rs: float) tuple[float, float][source]

Return (SR, SRinv) = (dI/dIs, (dI/dIs)^-1) at one bias point.

psc_sim.cell_metrics.rs_extraction_series(p: CellParameters, v0: float = 0.0, v1: float = 0.8, step: float = 0.02, rs: float | None = None, *, iv_backend: IVBackend | None = None) tuple[ndarray, ndarray][source]

Returns I_mA and SRinv = (dI/dIs)^-1.

psc_sim.cell_metrics.extract_rs_from_sr_inv(p: CellParameters, i_mA: ndarray | None = None, sr_inv: ndarray | None = None, mask: slice | ndarray | None = None, rs: float | None = None, *, iv_backend: IVBackend | None = None) float[source]

Linear regression SRinv vs I_mA; Rs = slope * vt * 1000.

psc_sim.cell_metrics.compute_metrics(p: CellParameters, pin_w_cm2: float = 0.1, *, iv_backend: IVBackend | None = None) CellMetrics[source]

Compute Voc/Isc/FF/PCE metrics using the same sampling as the HTML app.

LTspice bridge

LTspice .net deck generation (HTML-compatible topology).

psc_sim.ltspice_netlist.build_netlist(p: CellParameters, sim: Literal['dc', 'ac', 'tran'] = 'dc', spec: SweepSpec | None = None, ac_freqs: ndarray | None = None, tran_spec: TranSpec | None = None) str[source]

Return a .net deck matching solar_cell_sim_app.html renderSpice topology.

LTspice batch execution and .raw parsing.

Settings and user presets

User settings persisted as JSON (preset, sweep, Rs mask, LTspice path, optional params).

class psc_sim.settings.UserSettings(last_preset: 'str' = 'perovskite', iv_spec: 'SweepSpec' = <factory>, rs_fit_lo_mA: 'float' = 5.0, rs_fit_hi_mA: 'float' = 28.0, ltspice_exe: 'str | None' = None, custom_params: 'CellParameters | None' = None, tran_spec: 'TranSpec | None' = None, eis_plot_f_min_hz: 'float' = 1.0)[source]

Named user-defined parameter presets persisted beside settings.json.

class psc_sim.user_presets.UserPreset(id: 'str', label: 'str', params: 'CellParameters', note: 'str' = '')[source]

Metrics for comparing Python vs LTspice I–V curves.

psc_sim.compare_metrics.interpolate_current_at(v_grid: ndarray, v_samples: ndarray, i_samples: ndarray) ndarray[source]

Linear interpolation of I(V) onto v_grid; NaN outside sample range.

psc_sim.compare_metrics.iv_curve_rmse(v_ref: ndarray, i_ref: ndarray, v_other: ndarray, i_other: ndarray) tuple[float, float][source]

Return (rmse_A, max_abs_error_A) of i_other interpolated onto v_ref.

psc_sim.compare_metrics.eis_z_rmse(zre_a: ndarray, zim_a: ndarray, zre_b: ndarray, zim_b: ndarray) float[source]

RMSE on Re/Im components (same-length arrays).

psc_sim.compare_metrics.eis_impedance_rmse(freq_ref: ndarray, zre_ref: ndarray, zim_ref: ndarray, freq_other: ndarray, zre_other: ndarray, zim_other: ndarray) float[source]

RMSE of complex impedance (Re/Im) with other interpolated onto freq_ref.

EIS CSV and fitting

Load measured EIS CSV (see docs/conventions.md).

psc_sim.eis_csv.load_eis_csv(path: str | Path) tuple[ndarray, ndarray, ndarray][source]

Return (freq_hz, zreal_ohm, zimag_ohm). Accepts header aliases.

psc_sim.eis_csv.write_eis_csv(path: str | Path, freqs_hz: ndarray, zre: ndarray, zim: ndarray, *, zre_lt: ndarray | None = None, zim_lt: ndarray | None = None) None[source]

Write EIS CSV with canonical headers (see docs/conventions.md).

psc_sim.eis_csv.rmse_z(f_meas: ndarray, zre_meas: ndarray, zim_meas: ndarray, zre_model: ndarray, zim_model: ndarray) float[source]

RMSE on real and imag (same length arrays). f_meas is accepted for API stability.

Fit measured EIS to the lumped Rs + (Rsh||Cj) + (Rion + Cion) model using scipy.

psc_sim.eis_fit.fit_lumped_eis(freqs_hz: ndarray, zre_meas: ndarray, zim_meas: ndarray, p0: CellParameters, *, max_nfev: int = 500) tuple[CellParameters, Any][source]

Adjust Rs, Rsh, Cj, Rion, Cion to minimize (Zre,Zim) RMSE. I0,n,Iph,T are held fixed from p0.

CLI

psc-sim command-line interface.