Skip to content

Repository files navigation

☀️ SolarLab

Thin-Film Solar Cell Simulator

1D/2D Drift-Diffusion · Poisson · Mobile Ions · Transfer-Matrix Optics

Perovskite · CIGS · c-Si


PythonFastAPIViteLicense


Table of Contents

SectionDescription
1OverviewWhat SolarLab does
2Key FeaturesCapabilities at a glance
3Repository LayoutDirectory structure
4InstallationSetup instructions
5Running the ApplicationBackend + frontend startup
6Physical Principles & EquationsGoverning PDEs and physics
7Numerical MethodSolver architecture
8Validation & Model ScopeWhat current evidence does and does not certify
9Using the Web UIUI walkthrough
10Shipped Device PresetsAvailable YAML configs
11TestingTest suite overview
12ReferencesKey literature


Overview

SolarLab is a research-grade simulator for thin-film solar cells. The core is a one-dimensional drift-diffusion + Poisson + mobile-ion solver, with an experimental two-dimensional extension for lateral microstructure studies, backed by a FastAPI HTTP service and a Vite / TypeScript / Plotly single-page web application.

It reproduces the main thin-film characterisation experiments from a single device definition, grouped into four families:

FamilyExperimentWhat it does
Illuminated J-VJ-V sweepForward + reverse scans with ionic memory preserved (hysteresis); optional current decomposition ($J_n$ / $J_p$ / $J_\text{ion}$ / $J_\text{disp}$) and spatial profiles ($\varphi$, $E$, $n$, $p$, $P$) per voltage point
Suns-VocIntensity sweep producing the Sinton pseudo-JV (immune to series resistance) and pseudo-FF upper bound
Dark characterisationDark J-VInjection-current diode curve with auto-selected linear window → ideality factor $n$ and saturation current $J_0$
Mott-Schottky (C-V)Dark C-V + auto-windowed $1/C^2$ fit → built-in voltage $V_\text{bi}$ and effective dopant density $N_\text{eff}$
SpectralEQE / IPCEWavelength-resolved external quantum efficiency via monochromatic TMM; integrates against AM1.5G for $J_\text{sc}$
TransientImpedance spectroscopyLock-in extraction of $Z(f)$ with displacement current; Nyquist + Bode plots
Transient photovoltage (TPV)Small-signal light-pulse perturbation at open circuit; mono-exponential fit for recombination lifetime $\tau$
DegradationLong-time transient with a frozen-ion snapshot J-V at each probe, decoupling slow ionic drift from the instantaneous electronic response

The simulator works for perovskite cells (with mobile ions), inorganic thin films (CIGS, CdTe-style stacks), and crystalline silicon homojunctions — all through the same YAML-based device schema. A separate 2T monolithic tandem driver performs a combined TMM over top + junction + bottom, runs independent sub-cell J-V sweeps, and series-matches at a common current grid.

The current technical reference is the SolarLab Technical and User Manual (2026-08-11). It records the solver-specific variable sets, validation gates, and limitations that do not fit in this overview.

SolarLab architecture and data flow



Key Features

Core solver

CapabilityDetails
🧮Drift-diffusion coreScharfetter-Gummel finite-element fluxes on a tanh-clustered multilayer grid; Method-of-Lines with Radau implicit time integration
Poisson solvePre-factored LAPACK tridiagonal dgttrf/dgttrs (cached once per run) — ~40× faster than naive assembly
🔬Mobile ionsFinite-site modified-PNP flux with shared-site dual-ion support; legacy whole-flux regularization retained only for frozen comparisons
🏗️HeterostacksBand offsets from $\chi$ and $E_g$; per-interface transport/recombination; signed contact potential from semiconductor work functions, explicit metals, or a legacy benchmark override

Physics upgrades (Phase 2–4)

CapabilityDetails
🌈Transfer-matrix opticsCoherent TMM for position-resolved $G(x)$ with the Poynting-vector correction so $R+T+A=1$; AM1.5G-weighted per-wavelength absorption
🔥Interface transportDefault SG face with an optional thermionic cap; supported ion-free QF runs can instead use an exclusive zero-thickness interface boundary with reciprocal transport and shared interface-SRH occupancy
💡Photon recyclingYablonovitch single-pass escape probability $P_\text{esc} = \min(1, 1/(4 n^2 \alpha d))$ scales $B_\text{rad}$ per absorber (Phase 3.1)
🔁Self-consistent reabsorptionPer-RHS radiative reabsorption feeds the trapped emission fraction back into $G(x)$ on absorber nodes (Phase 3.1b, FULL only)
🌀Field-dependent mobilityCaughey-Thomas velocity saturation + Poole-Frenkel hopping applied per RHS from the Poisson face field (Phase 3.2, FULL only)
🚪Selective / Schottky contactsRobin-type flux $J = \pm q S (n - n_\text{eq})$ at outer contacts; $S \to \infty$ is ohmic, $S = 0$ is blocking (Phase 3.3, FULL only)
🎯Position-dependent trapsExponential or Gaussian defect profiles concentrated at transport-layer interfaces; $\tau(x) = \tau_\text{bulk} \cdot N_{t,\text{bulk}} / N_t(x)$ (Phase 4a)
🌡️Temperature scalingVarshni bandgap shift referenced to 300 K and power-law $B_\text{rad}(T) \propto (T/300)^\gamma$; self-consistent with $n_i(T)$ (Phase 4b)
🧭Explicit solver driversTransient, direct steady-state, cancellation-safe quasi-Fermi, QF frequency-domain, and 2D drivers keep distinct variable sets and fail closed on unsupported physics

Tiered fidelity modes

TierPhysics setUse case
LEGACYNo TE, no TMM, uniform traps, $T = 300$ K — classic IonMonger reproductionRegression parity against published benchmarks
FASTAll build-once upgrades on (TE, TMM, dual ions, trap profile, $T$-scaling, photon recycling); per-RHS hooks offDefault for J-V / impedance / degradation sweeps
FULLWidest configured hook set, including per-RHS radiative reabsorption, $\mu(E)$, and Robin contactsRuns that require those hooks; accuracy still depends on driver support and validation

The tier is a feature ceiling, not an accuracy grade. The selected experiment driver still determines the unknowns, supported physics, and required certification checks.

Experiments

CapabilityDetails
📈J-V sweepForward + reverse scans with ionic memory preserved (hysteresis from physics, not post-processing); optional current decomposition and spatial-profile export
☀️Suns-VocIntensity sweep → Sinton pseudo-JV (series-resistance-free) and pseudo-FF upper bound
🌓Dark J-VAuto-windowed linear fit on $\ln J$ vs $V$ → ideality factor $n$ and saturation current $J_0$
📊Mott-SchottkyDark C-V with $1/C^2$ linearisation → built-in voltage $V_\text{bi}$ and effective dopant density $N_\text{eff}$
🌈EQE / IPCEWavelength-resolved external quantum efficiency via monochromatic TMM; AM1.5G integration yields $J_\text{sc}$
🎵ImpedanceLock-in $Z(f)$ with displacement current; Nyquist + Bode output
Transient photovoltageSmall-signal light pulse at open circuit with mono-exponential fit for recombination lifetime $\tau$
🧊Frozen-ion degradationLong-time transient with snapshot J-V at each probe ($D_\text{ion} \to 0$) — decouples ionic drift from electronic response
🔗2T tandem driverCombined TMM over top + junction + bottom, independent sub-cell sweeps, series-matched on a common current grid
🟦2D J-V sweepTensor-product 2D extension for lateral-uniform parity checks and microstructure / grain-boundary studies
🟪Voc(Lg) grain sweepRepeats 2D J-V over grain sizes to quantify microstructure-driven open-circuit-voltage loss

Dimensionality

CapabilityDetails
📏1D drift-diffusion (default)All experiments above run on a tanh-clustered multilayer 1D grid with the cached MaterialArrays hot path
🟦2D Stage A (lateral-uniform)Tensor-product (Ny × Nx) grid with sparse 5-point Poisson, vectorised 2D Scharfetter-Gummel fluxes, periodic / Neumann lateral BCs; bootstraps from the 1D illuminated steady state and freezes ions as a static Poisson background. On a laterally-uniform stack the 2D solver reproduces the 1D J-V to within sub-mV $V_\text{oc}$, $5 \times 10^{-4}$ relative $J_\text{sc}$, and $10^{-3}$ FF — pinned by tests/regression/test_twod_validation.py. Available as kind='jv_2d' from the backend and as the J-V Sweep (2D) entry in the workstation experiment selector.
🟪2D Stage B (microstructure)Vertical grain boundaries with reduced SRH lifetime $\tau_\text{GB}$ painted onto the absorber via a YAML microstructure: block; non-empty Microstructure produces a heterogeneous $(\tau_n, \tau_p)(y, x)$ on MaterialArrays2D. The shipped nip_MAPbI3_singleGB preset (one centred GB, $\tau_\text{GB}=50$ ns, width 5 nm) drops $V_\text{oc}$ by ~46 mV vs the laterally-uniform baseline — pinned to a 5–100 mV regression window in tests/regression/test_twod_microstructure.py. The headline voc_grain_sweep experiment maps $V_\text{oc}(L_g)$ over a sequence of grain sizes and is exposed as kind='voc_grain_sweep' from the backend / Voc(L_g) Grain Sweep entry in the workstation.

Tooling

CapabilityDetails
🖥️Interactive web UILive SSE streaming, grouped experiment dropdown, GoldenLayout dockable panes, Plotly plots, layer-builder editor
🧪Full test suiteUnit, integration, and physics regression tests ($V_\text{oc}$ / $J_\text{sc}$ / FF / HI bounds; TMM $R+T+A$ conservation; 1D ↔ 2D parity gate)


Repository Layout

SolarLab/
├── .claude/skills/ Domain-reference skills; not simulator runtime code
├── .dockerignore Docker build-context exclusions
├── .githooks/ Repository hook scripts
├── .gitignore Repository ignore rules
├── docs/ Cross-tree manuals, validation reports, plans, figures
│ ├── manual/ Manual source, dated PDFs, and generated figures
│ ├── reference/ Canonical SCAPS comparison reports
│ ├── figures/ Stored comparison plots
│ ├── autoloop/ Research-loop ledger
│ ├── plans/ Cross-tree implementation plans
│ └── superpowers/ Historical specs, plans, and references
├── outputs/ Tracked example and comparison outputs
├── perovskite-sim/ Main simulator package
│ ├── perovskite_sim/ Python simulation library
│ │ ├── models/ Device schema and configuration loading
│ │ ├── physics/ Poisson, transport, recombination, ions, optics
│ │ ├── discretization/ Grid and Scharfetter-Gummel operators
│ │ ├── solver/ Transient and nonlinear solver infrastructure
│ │ ├── experiments/ 1D, QF, spectral, transient, and tandem drivers
│ │ ├── twod/ Experimental 2D solver and experiments
│ │ ├── scaps_compat/ SCAPS import and compatibility boundary
│ │ ├── screening/ Screening workflows
│ │ ├── sweeps/ Parameter-sweep helpers
│ │ ├── validation/ Grid-convergence utilities
│ │ └── data/ Spectra, optical constants, and references
│ ├── backend/ FastAPI service and SSE job dispatch
│ ├── frontend/ Vite + TypeScript + Plotly workstation
│ ├── configs/ Shipped 1D, tandem, and 2D YAML presets
│ ├── reproducibility/ Evidence registry and frozen P0/P1 records
│ ├── docs/ Package-specific technical documentation
│ ├── scripts/ CLI, validation, plotting, and import tools
│ ├── tests/ Unit, integration, regression, and validation tests
│ ├── notebooks/ Exploratory benchmarks
│ └── pyproject.toml Python package and test configuration
├── scripts/ Repository-level support scripts
├── docker-compose.yml Backend/frontend development stack
├── DFTSimulatorPV.pptx Project presentation artifact
├── CLAUDE.md Repository guidance
├── skills-lock.json Skill dependency lock
└── README.md This file


Installation

Prerequisites

  • Python 3.10+ (tested on 3.13)
  • Node.js 18+ with npm
  • A C compiler + BLAS/LAPACK (bundled with numpy/scipy wheels on most platforms)

1. Clone the repository

git clone https://github.com/ShaneLogic/SolarLab.git
cd SolarLab

2. Install the Python package

cd perovskite-sim
pip install -e ".[dev]"

This installs perovskite_sim as an editable package along with numpy, scipy, fastapi, uvicorn, pytest, and the dev tooling.

💡 Recommended: use a virtual environment.

python -m venv .venv &&source .venv/bin/activate

3. Install frontend dependencies

cd frontend
npm install

4. Verify the install

# From perovskite-sim/
pytest # default unit + integration (~2-3 min)
pytest -m validation -W error::RuntimeWarning # literature-informed lanes
pytest -m slow -W error::RuntimeWarning # heavy physics suite; can exceed 1 h
python scripts/verify_reproducibility.py --json # registry and frozen-baseline checks


Running the Application

SolarLab runs as two processes: a FastAPI backend that executes the simulations, and a Vite dev server that serves the UI.

Start the backend

From the SolarLab root (not perovskite-sim/):

uvicorn backend.main:app \
--host 127.0.0.1 --port 8000 \
--app-dir perovskite-sim --reload

Check it is alive:

curl http://127.0.0.1:8000/api/configs

You should get JSON listing the shipped presets.

Start the frontend

In a second terminal:

cd perovskite-sim/frontend
npm run dev

Open http://127.0.0.1:5173 in your browser. The frontend defaults to http://127.0.0.1:8000 and can be pointed at another backend with VITE_API_BASE. CORS is enabled for the local development and preview flows.

Run a simulation from Python (no UI)

fromperovskite_sim.models.config_loaderimportload_device_from_yamlfromperovskite_sim.experiments.jv_sweepimportrun_jv_sweepstack=load_device_from_yaml("configs/nip_MAPbI3.yaml")
result=run_jv_sweep(stack, N_grid=80, n_points=40, v_rate=1.0)
print(f"PCE: {result.metrics_fwd.PCE*100:.2f} %")
print(f"V_oc: {result.metrics_fwd.V_oc:.3f} V")
print(f"J_sc: {result.metrics_fwd.J_sc:.1f} A/m²")
print(f"FF: {result.metrics_fwd.FF:.3f}")
print(f"Hysteresis index: {result.hysteresis_index:.3f}")


Physical Principles & Equations

Physical Model Overview

Electrical coordinate, layer order, and contact-potential sources

Band bending, quasi-Fermi levels, and abrupt-interface closures

Governing Equations

SolarLab solves the coupled Poisson + drift-diffusion + mobile-ion system on the default one-dimensional device axis. The 2D extension extrudes the same layer stack onto a tensor-product grid for lateral microstructure studies. State variables at every grid node are the electron density $n$, hole density $p$, and the mobile-ion density $P$.


1. Poisson's Equation

The electrostatic potential $\varphi(x,t)$ satisfies

$$ -\frac{\partial}{\partial x}\left(\varepsilon_0 \varepsilon_r(x)\frac{\partial \varphi}{\partial x}\right) = q\bigl(p - n + N_D(x) - N_A(x) + P - P_0(x)\bigr) $$

with Dirichlet boundaries

$$ \varphi(0,t) = 0, \qquad \varphi(L,t) = V_{\text{bi}} - V_{\text{app}}(t). $$

$V_{\text{bi}}$ is computed from the Fermi-level offset across the heterostack (DeviceStack.compute_V_bi()), accounting for $\chi$, $E_g$, doping, and $n_i$. The operator is discretized with a harmonic-mean face permittivity and pre-factored once per run (LAPACK dgttrf), then solved at every RHS call with a single dgttrs sweep.


2. Carrier Continuity (Drift-Diffusion)

Electrons and holes obey

$$ \frac{\partial n}{\partial t} = \frac{1}{q}\frac{\partial J_n}{\partial x} + G(x) - R(n,p,x), \qquad \frac{\partial p}{\partial t} = -\frac{1}{q}\frac{\partial J_p}{\partial x} + G(x) - R(n,p,x) $$

with the conventional drift-diffusion fluxes

$$ J_n = q\mu_n n E + qD_n\frac{\partial n}{\partial x}, \qquad J_p = q\mu_p p E - qD_p\frac{\partial p}{\partial x}, \qquad E = -\frac{\partial\varphi}{\partial x}. $$

Scharfetter–Gummel discretization. The flux between nodes $i$ and $i+1$ is written so it is exact for a constant-flux, exponential-profile solution of the local drift-diffusion equation:

$$ J_{n,,i+\tfrac12} = \frac{qD_n}{\Delta x_i}\Bigl[n_{i+1},B(\Delta\varphi_i/V_t) - n_i,B(-\Delta\varphi_i/V_t)\Bigr] $$

where $B(x) = x/(e^{x}-1)$ is the Bernoulli function and $V_t = k_B T / q$. This removes the classical upwind/central-difference stability problem when $|E|\Delta x \gg V_t$.


3. Recombination

The net recombination rate is the sum of Shockley–Read–Hall, radiative (bimolecular), and Auger channels:

$$ R = R_{\text{SRH}} + R_{\text{rad}} + R_{\text{Auger}} $$

$$ R_{\text{SRH}} = \frac{np - n_i^2}{\tau_p(n + n_1) + \tau_n(p + p_1)}, \qquad R_{\text{rad}} = k_{\text{rad}},(np - n_i^2), \qquad R_{\text{Auger}} = (C_n n + C_p p)(np - n_i^2). $$

Interface recombination is applied at heterointerfaces via the per-interface surface-recombination velocities $(v_n, v_p)$ carried in DeviceStack.interfaces.


4. Mobile-Ion Migration (Finite-Site Modified PNP)

For perovskite cells, the default finite-site modified Poisson-Nernst-Planck model applies lattice-gas crowding to chemical diffusion without multiplying the electrostatic drift. For one positive species,

$$ F_P=-D_{\text{ion}}\left[ \frac{1}{1-P/P_{\text{lim}}}\frac{\partial P}{\partial x} +\frac{P}{V_t}\frac{\partial \varphi}{\partial x} \right]. $$

Dual mobile species share the occupancy $\theta=(P_+ + P_-)/P_{\text{lim}}$ by default. The legacy tier retains the superseded whole-flux steric multiplier for frozen benchmark compatibility. Non-perovskite stacks set $D_{\text{ion}}=0$ so ionic dynamics drop out.


5. Optical Generation

Two optional models share the same interface.

Beer–Lambert (default / fallback):

$$ G_{\text{BL}}(x) = \int_\lambda \alpha(\lambda,x)\cdot\Phi_0(\lambda)\cdot \exp\left[-\int_0^x \alpha(\lambda,\xi),d\xi\right],d\lambda. $$

Transfer-matrix (coherent thin-film) optics. When any layer carries an optical_material key, physics/optics.py loads complex $n(\lambda)$, $k(\lambda)$ CSVs from perovskite_sim/data/nk/, builds the layer transfer matrices against AM1.5G, and computes the position-resolved generation rate as

$$ G_{\text{TMM}}(x,\lambda) = \frac{1}{\hbar\omega}\cdot\frac{n(x,\lambda)}{n_{\text{amb}}}\cdot\alpha(x,\lambda),|E(x,\lambda)|^2, $$

integrated over the AM1.5G spectrum. The $n/n_{\text{amb}}$ prefactor is the Poynting-vector correction that guarantees $R+T+A=1$. G_TMM(x) is computed once during build_material_arrays and cached on MaterialArrays.G_optical — the hot path never recomputes optics.


6. Thermionic Emission at Heterointerfaces

The default density-variable discretization keeps one bidirectional Scharfetter-Gummel face and may apply an empirical density-weighted thermionic cap when a band offset exceeds 0.05 eV. The dimensionally normalized Richardson-Dushman form is available through te_physical_norm, but remains opt-in while strongly binding interface cases are still being conditioned:

$$ J_{\text{TE},n} = A^*_n T^2 \exp\left(-\frac{\Delta E_c}{k_B T}\right)\bigl[\exp(qV/k_B T) - 1\bigr], $$

For the supported ion-free local model, solver="quasi_fermi" with interface_boundary=true instead removes the ordinary SG face and solves reciprocal thermionic transport and shared-occupancy interface SRH on an exclusive zero-thickness boundary. Unsupported physics fails before Newton starts. The current CBO campaign certifies its numerical grid contraction, not external SCAPS agreement; see Validation & Model Scope.


7. Boundary Conditions at the Contacts

By default, each outer carrier contact is treated as ohmic: the boundary carrier densities are fixed to the thermal-equilibrium values computed from the outermost layers and the boundary-node time derivatives are pinned. In FULL mode, any configured selective-contact coefficient replaces that side/carrier pin with a Robin-type flux:

$$ J_{c,s} = \sigma_{c,s},qS_{c,s}(u_c - u_{c,\mathrm{eq}}), \qquad c \in {n,p},\ s \in {\mathrm{left},\mathrm{right}}, $$

where $u_n=n$, $u_p=p$, and $\sigma_{c,s}$ is the outward-current sign for the carrier and side. The YAML schema supports either flat keys (S_n_left, S_p_left, S_n_right, S_p_right) or the readable nested form:

device:
mode: fullcontacts:
left:
S_p: 1.0e3S_n: 1.0e-3right:
S_n: 1.0e3S_p: 1.0e-3

Missing or null means the default ohmic Dirichlet pin remains active for that carrier/side. A finite value activates the Robin flux; S = 0 is blocking, while large S approaches the ohmic limit. The same coefficients are mapped onto the top and bottom boundaries of the 2D solver.

The Poisson contact potential is configured independently through built_in_potential_mode: new devices can derive the signed value from endpoint semiconductor work functions or from two explicit metal work functions, while legacy_manual preserves a published benchmark magnitude. Positive applied bias reduces the built-in field for either contact orientation:

$$ \varphi(0)=0,\qquad \varphi(L)=V_{\text{bi}}^{\text{bc}}-sV_{\text{app}}, \qquad s=\operatorname{sign}(V_{\text{bi}}^{\text{bc}}). $$

Ions are blocked at both contacts ($J_P = 0$), enforcing ionic-species conservation.


8. Two-Dimensional Extension

The 2D solver is an experimental extension for lateral microstructure effects, not a replacement for the default 1D workflow. It builds a tensor-product grid with lateral coordinate $x$ and vertical stack coordinate $y$, solves sparse 2D Poisson with Dirichlet contacts in $y$ and periodic lateral boundaries, and advances 2D Scharfetter-Gummel fluxes for carriers. Ions are frozen as a static Poisson background during 2D J-V runs.

2D presets live under perovskite-sim/configs/twod/ and are auto-discovered by the backend. Current entry points are kind='jv_2d' for a voltage sweep and kind='voc_grain_sweep' for a grain-size sweep. Microstructure is supplied by a YAML microstructure: block containing vertical grain boundaries; the solver paints reduced SRH lifetimes into the absorber through MaterialArrays2D.



Numerical Method

IngredientChoice
Driver topologyExplicit transient, direct steady-state, quasi-Fermi DC, QF frequency-domain, and 2D paths
1D gridTanh-clustered multilayer grid (refined near interfaces and contacts)
2D gridTensor-product lateral/vertical mesh for Stage A/B microstructure studies
SpatialScharfetter-Gummel finite elements for drift-diffusion; harmonic-mean faces for Poisson
TransientMethod of Lines with scipy.integrate.solve_ivp and the Radau IIA 5th-order implicit method
Steady stateDirect density-log Newton or cancellation-safe quasi-Fermi Newton, selected explicitly
PoissonLAPACK dgttrf/dgttrs tridiagonal LU, pre-factored once per run
CertificationDriver-specific residual, cell-current, all-face admittance, or lateral/vertical diagnostics
Safety capmax_step capped on every run_transient sub-interval to prevent Radau from accepting a giant step near flat-band

All physical data is held in immutable frozen dataclasses (MaterialParams, LayerSpec, DeviceStack, SolverConfig). In-place mutation is forbidden — updates use dataclasses.replace(...).

Numerical drivers, variable sets, and certification paths



Validation & Model Scope

SolarLab keeps repository capability, internal numerical acceptance, and external physical agreement as separate claims. The machine-readable registry and reproduction commands live under perovskite-sim/reproducibility/; the full interpretation is in the 2026-08-11 manual.

Registered c-Si QF J-V and C-V grid-ladder observations

The c-Si panels are internal numerical evidence for the restricted local QF model. They do not certify the general transient driver or fit an external c-Si device.

Physical-interface CBO response, grid contraction, and certification gates

The physical-interface CBO scan passes its registered N=40/50/60 numerical grid envelope, but the normalized SCAPS-shape error is 0.4744 against a 0.05 gate. Its result is numerical_certified=true and top-level certified=false.

Registered 1D and 2D parity domain and current model scope

The registered 1D/2D comparison uses matched vertical grids, periodic lateral boundaries, frozen ions, and an interface-free preset. Mobile-ion dynamics and the 1D interface-SRH/physical-QF machinery are outside that parity claim.



Using the Web UI

After launching the backend and frontend (see Running the Application), open http://127.0.0.1:5173. The UI is split into three regions.

Layout

Web UI Layout

Left Rail — Devices / Results

ElementDescription
DEVICESActive device tab with simulation tier (FAST / FULL / LEGACY). Click to focus configuration.
RESULTS / COMPARECompleted runs are archived here. Select two or more to overlay plots.

Center Pane — Device Configuration

ElementDescription
Preset dropdownChoose a shipped or user preset. Switching reloads the stack from YAML.
ResetDiscards unsaved edits and re-loads the last saved version.
Stack Visualizer(Full tier) Vertical layer column — click to edit, + to insert, drag to reorder, x to delete.
Detail EditorCollapsible groups: Geometry, Transport, Recombination, Ions & Optics.
TMM badgeAppears when any layer has a non-empty optical_material.
Save AsSave edited stack to configs/user/ or download YAML directly.
Detail Editor parameter groups
  • Geometry — thickness, grid density, role (contact / transport / absorber / substrate)
  • Transport$\mu_n$, $\mu_p$, $N_c$, $N_v$, $N_A$, $N_D$, $\chi$, $E_g$, $\varepsilon_r$
  • Recombination$\tau_n$, $\tau_p$, $k_{\text{rad}}$, $C_n$, $C_p$, $E_t$
  • Ions & Optics$D_{\text{ion}}$, $N_{\max}$, $P_0$, optical_material, n_optical, incoherent flag
  • Contacts / Advanced physics — optional Robin contact coefficients, field mobility, and FULL-tier hooks

Right Pane — Experiments

Experiment panes share a common pattern: parameters form -> Run button -> live progress bar -> Plotly plot. The workstation includes the 1D characterisation experiments plus 2D J-V and grain-size studies.

J-V Sweep

ParameterDescription
$N_{\text{grid}}$Number of spatial nodes
V sample pointsNumber of voltage samples per scan direction
Scan rate (V/s)Ionic memory effects — fast scans produce larger hysteresis
$V_{\max}$Upper voltage bound (defaults to $V_{\text{bi}}$)
Decompose currentPer-face breakdown into $J_n$ / $J_p$ / $J_\text{ion}$ / $J_\text{disp}$ at every voltage
Save spatial profilesSnapshot $\varphi(x)$, $E(x)$, $n(x)$, $p(x)$, $P(x)$ at each voltage

The experiment runs a forward scan (short-circuit to $V_{\max}$) immediately followed by a reverse scan, reusing the final state so the ionic population is preserved across the turn. Output: overlaid forward/reverse curves plus metric cards for $V_{\text{oc}}$, $J_{\text{sc}}$, FF, PCE, and hysteresis index. For a dark diode curve, use the dedicated Dark J-V experiment, which runs the same $G = 0$ sweep and adds an ideality-factor / $J_0$ fit.

The two optional output views (decomposition, spatial profiles) are mutually exclusive on a single run — pick one per sweep or re-run for the other.

Impedance

ParameterDescription
Frequency sweep$\omega_{\min}$, $\omega_{\max}$, $N_\omega$
DC biasSteady-state bias voltage
AC amplitudeSmall-signal perturbation

At each frequency, the solver integrates several AC cycles, then a lock-in amplifier extracts amplitude and phase. Displacement current $\varepsilon_0 \varepsilon_r , \partial E / \partial t$ is included. Output: Nyquist plot and Bode magnitude/phase curves.

Degradation

ParameterDescription
Total timeSimulation duration
Number of probesSnapshot count over the simulation
Probe biasVoltage for snapshot J-V

At each probe time, the solver takes a frozen-ion snapshot: a copy of the stack with $D_{\text{ion}}=0$ measures the instantaneous $J(V)$ response. This decouples slow ionic drift from the electronic response. Output: PCE / $V_{\text{oc}}$ / $J_{\text{sc}}$ vs aging time.

Transient Photovoltage (TPV)

ParameterDescription
$N_{\text{grid}}$Number of spatial nodes
$\delta G$ fractionFractional generation perturbation (e.g. 0.05 = 5% pulse)
Pulse durationDuration of the light pulse [s]
Observation windowTotal time including decay [s]

The device is equilibrated at open circuit under steady illumination, then a small light pulse is applied. The voltage transient $V(t)$ decays back to $V_\text{oc}$ as excess carriers recombine. A mono-exponential fit extracts the effective recombination lifetime $\tau$. Output: $V(t)$ decay curve, $J(t)$ transient, fitted $\tau$.

2D J-V and Grain Sweep

ParameterDescription
Nx, Ny_per_layerLateral and vertical mesh density
Lateral lengthWidth of the 2D domain
MicrostructureOptional grain_boundaries block painted into absorber lifetime fields
Grain sizesSequence used by voc_grain_sweep to compute $V_\text{oc}(L_g)$

The 2D J-V pane uses the same metric semantics as the 1D J-V sweep. If the voltage window does not bracket the zero-current crossing, the UI shows V_oc not bracketed and keeps raw data unchanged while offering an operational-range display clip.

Docs Tabs — Tutorial & Algorithm

The Tutorial pane is a guided walkthrough (Device Setup -> First Simulation -> Interpreting Results -> Advanced Topics). The Algorithm pane is a formal write-up of the PDEs, discretization, solver tiers, and the transfer-matrix optical model. Both are always available — no backend required.



Shipped Device Presets

Presets live in perovskite-sim/configs/, with 2D examples under perovskite-sim/configs/twod/. Drop a new .yaml file in either location and it is auto-discovered by GET /api/configs. The table below lists representative presets; the reproducibility registry inventories every shipped YAML.

PresetMaterial SystemIonsOpticsNotes
nip_MAPbI3MAPbI3 n-i-pYesBeer-LambertCanonical perovskite reference
pin_MAPbI3MAPbI3 p-i-nYesBeer-LambertInverted-architecture reference
nip_MAPbI3_tmmMAPbI3 n-i-p + glassYesTMMTMM-enabled with 1 mm substrate
pin_MAPbI3_tmmMAPbI3 p-i-n + glassYesTMMTMM-enabled
ionmonger_benchmarkCourtier 2019YesBeer-LambertIonMonger cross-check
driftfusion_benchmarkDriftfusion paramsYesBeer-LambertDriftfusion cross-check
cigs_baselineZnO / CdS / CIGSNoBeer-Lambert$D_{\text{ion}}=0$ everywhere
cSi_homojunctionn+ / p c-SiNoBeer-LambertRequires the cancellation-safe QF driver for production J-V
csi_vannijen2025_pn_cvGaussian p+ / n c-SiNoDark onlyPartial external C-V comparison; not pointwise parity
twod/nip_MAPbI3_uniformMAPbI3 2D lateral-uniformFrozen in 2DBeer-Lambert2D parity / baseline preset
twod/nip_MAPbI3_singleGBMAPbI3 2D single grain boundaryFrozen in 2DBeer-LambertMicrostructure Voc-loss preset
twod/bcx_combined_demoMAPbI3 2D combined hooksFrozen in 2DBeer-LambertDemo for Robin contacts + field mobility + microstructure


Testing

# From perovskite-sim/
pytest # default unit + integration (~2-3 min)
pytest -m validation -W error::RuntimeWarning # literature-informed lanes
pytest -m slow -W error::RuntimeWarning # heavy physics suite; can exceed 1 h
python scripts/verify_reproducibility.py --json # registry and frozen baselines
pytest --cov=perovskite_sim --cov-report=term-missing # optional coverage report
SuiteScope
UnitPer-module physics + solver coverage
IntegrationEnd-to-end experiment runs on shipped presets
RegressionPhysical sanity envelopes ($V_{\text{oc}}$, $J_{\text{sc}}$, HI bounds); BLAS pinned via conftest.py

Autoloop guardian (python perovskite-sim/scripts/autoloop_run.py --once) — sense-and-record cycle: runs the test ladder, scores SolarLab vs the SCAPS reference, and records open gaps to the ledger.

  • Autoloop attribution (python perovskite-sim/scripts/autoloop_run.py --attribute) — diagnoses the top open gap (bug / numerics / physics / uncertain) by ablating physics flags + grid + dark-current probes; records a Hypothesis. Read-only.
  • Autoloop implement (python perovskite-sim/scripts/autoloop_run.py --implement [--apply]) — promotes a confirmed lever flag in the parity config, runs the full gate stack (legacy-bit-identical + parity-improved + reconciliation), and lands it on the current branch only on --apply. Dry-run by default.
  • Autoloop boulder (python perovskite-sim/scripts/autoloop_run.py --boulder [--converge]) — the continuous driver: sweep drains gaps into a proposal report; --converge auto-applies fixes and loops until the parity target (on an autoloop branch, never main).
  • Autoloop L3 lab data — point --reference at a tiered descriptor (scaps_lab_tiered.json) to score absolutes against measured J-V (LabReferenceSource) while keeping SCAPS trend sweeps. Default stays pure-SCAPS.
  • Autoloop design-search (python perovskite-sim/scripts/autoloop_run.py --search) — parity-gated, advisory search of the device-design space for max-PCE designs (no-dep random search; pluggable for optuna later). Reports designs; applies nothing.
  • Autoloop G5 verify (--attribute --llm --verify) — N diverse-lens skeptics adversarially adjudicate an LLM novel-cause lead: survives → confirmed (actionable), majority-refute → refuted + recorded as never-retry. Opt-in.
  • Autoloop codegen (--codegen --llm) — for a confirmed cause with no existing flag, an LLM writes a flag-gated band-parameter lever; the spine verifies it (build + flag-OFF bit-identical + flag-ON improves) and commits it to a fresh branch for human merge. Opt-in, default OFF.


References

  1. Scharfetter, D. L. & Gummel, H. K. (1969) — Large-signal analysis of a silicon Read diode oscillator. Foundational SG flux scheme.
  2. Courtier, N. E. et al. (2019) — IonMonger: a free and fast planar perovskite solar cell simulator with coupled ion vacancy and charge carrier dynamics. J. Comput. Electron.
  3. Calado, P. et al.Driftfusion: an open source code for simulating ordered semiconductor devices. Reference MATLAB implementation used for cross-checking.
  4. Pettersson, L. A. A. et al. (1999) — Modeling photocurrent action spectra of photovoltaic devices based on organic thin films. J. Appl. Phys.
  5. Burkhard, G. F. et al. (2010) — Accounting for interference, scattering, and electrode absorption to make accurate internal quantum efficiency measurements in organic and other thin solar cells. Adv. Mater.
  6. Richardson, G. et al. — Theoretical basis for ion-migration modelling in perovskite solar cells.

Made with 🧪 and ☕ at HKUST Guangzhou · SolarLab

About

Perovskite solar cell drift-diffusion simulator: Python physics core (TMM optics, ion migration, selective contacts), FastAPI streaming backend, and Vite/TypeScript frontend with an interactive Layer Builder.

Topics

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages