Spatially and temporally correlated uncertainty, autonomous inverter control and active distribution network operation in a real SimBench low-voltage grid.
The maximum photovoltaic (PV) capacity a low-voltage (LV) feeder can accommodate is classically determined deterministically: PV is scaled uniformly across all customers, one worst-case day is simulated, and the capacity at which the first limit is violated is declared the hosting capacity. That procedure answers a question nobody asks in practice. Real PV fleets grow where individual customers decide to invest, with heterogeneous system sizes, different orientations, single- or three-phase connections, and they operate under weather that is strongly correlated in space and time. A number that assumes all of this away cannot state how likely a violation is, how severe it would be, where and when it occurs, or which limit binds first.
This repository implements a probabilistic assessment that answers those questions on a real SimBench LV grid, and quantifies how much additional capacity autonomous inverter control and active network operation unlock — including their cost, their losses and whether the resulting curtailment is distributed fairly.
The study is a from-scratch reimplementation of the author's earlier work on probabilistic hosting capacity:
S. Kortmann, X. Han, M. Schwarz and G. Hug, "From a distributing to a generating network: Assessing PV hosting capacity under uncertainty in distribution grids", PESS 2021; Power and Energy Student Summit, 2021, pp. 1–6. IEEE Xplore 9735254
That paper motivated this repository. The reimplementation goes beyond it in four respects: uncertainty is spatially and temporally correlated through an explicit latent Gaussian model rather than sampled independently; phase imbalance is modelled physically with unbalanced three-phase power flow instead of a balanced approximation; curtailment fairness is quantified alongside efficiency; and every assumption, confidence interval and unsupported risk level is machine-recorded so a result can be traced back to what produced it.
- How does deterministic PV hosting capacity differ from probabilistic hosting capacity?
- How strongly does uncertain spatial PV allocation affect hosting capacity?
- How strongly do temporal and spatial correlations affect the result compared with independent sampling?
- Which technical constraint is limiting — overvoltage, undervoltage, line loading, transformer loading, voltage unbalance, or non-convergence?
- How much can autonomous inverter control increase hosting capacity?
- How much additional capacity is enabled by OLTC operation and coordinated curtailment?
- What are the associated network losses, curtailed energy, reactive-power demand, tap operations, intervention costs and fairness impacts?
- Does a balanced power-flow analysis systematically over- or underestimate hosting capacity compared with an unbalanced three-phase analysis?
- Which buses have the greatest marginal impact on voltage and loading?
- How stable is the estimated hosting capacity with respect to Monte Carlo sample size?
| # | Hypothesis |
|---|---|
| H1 | Deterministic uniform PV scaling does not adequately represent hosting capacity under uncertain spatial PV allocation. |
| H2 | Neglecting temporal and spatial correlation produces materially different risk estimates. |
| H3 | Local Q(U) control increases hosting capacity but can increase reactive-power flows and losses. |
| H4 | Coordinated operation of Q(U), OLTC and curtailment provides a larger increase than each individual measure. |
| H5 | A small amount of targeted curtailment can generate a disproportionate capacity increase. |
| H6 | Sensitivity-based curtailment minimises total curtailed energy but can distribute curtailment unfairly. |
| H7 | A fairness-aware curtailment policy can reduce inequality with a limited efficiency penalty. |
| H8 | Balanced analysis can conceal phase-specific voltage and loading violations. |
| H9 | Electrical distance alone is not sufficient to explain the marginal network impact of PV at a bus. |
Each hypothesis is evaluated against an explicit numerical criterion in
reports/study_report.md and reported as supported, partially supported or not
supported, together with the numbers that led to the verdict. Nothing is asserted that
the generated tables do not support.
| definition | formulation |
|---|---|
| deterministic | |
| joint chance constrained | |
| severity constrained | $P^{R}{\mathrm{HC}} = \max\{P : \mathrm{CVaR}{\alpha}(S(P,\omega)) \le R_{\max}\}$ |
| conservative | as chance constrained, but the upper confidence bound of the estimated probability must satisfy the limit |
The violation event includes a curtailment budget. Under unrestricted curtailment the hosting capacity of any curtailment strategy is unbounded — arbitrarily much PV can be installed and then thrown away, which is mathematically true and practically meaningless. An operating point that curtails more than
limits.max_curtailment_shareof the available PV energy therefore counts as violated. The default of 3 % follows the share of annual generation that German feed-in management may curtail without compensation; it is a regulatory-practice assumption, is reported as its own violation type (curtailment_budget), and can be disabled — in which case the unboundedness is visible in the results rather than hidden.
integrated over time into p.u.-hours, percentage-point-hours and VUF-hours. Power-flow non-convergence is a separate outcome class, never silently converted into "feasible".
A latent Gaussian field with a grid-wide, a spatially correlated and an individual component, each stationary AR(1) in time:
with
Independent white noise exists only as an explicitly labelled reference ensemble used to quantify the effect of neglecting correlation (H2). The correlation structure is validated against its targets before any result is produced, on a dedicated long series — short study windows give strongly biased sample autocorrelations, which is a property of the estimator, not of the generator.
Bernoulli adoption
so that a capacity sweep isolates the effect of where PV is connected from variation in how much is installed. An unconstrained-adoption mode exists but its results are never mixed with the capacity-preserving analysis.
Phase imbalance is modelled physically, not by perturbing balanced results: loads and PV
systems are assigned to phases and represented by pandapower asymmetric_load /
asymmetric_sgen elements, conserving power exactly,
and the voltage unbalance factor is evaluated from the sequence voltages, $\mathrm{VUF}{i,t} = |V{2,i,t}|/|V_{1,i,t}| \cdot 100,%$.
SimBench does not ship the data an unbalanced power flow needs. The line standard types contain no zero-sequence parameters, the transformer standard types leave the vector group and the zero-sequence short-circuit data undefined, and pandapower 3.x's
tap_changer_typediscriminator is absent — which makes pandapower ignore the tap position entirely, so an OLTC controller would run without any effect. The code detects each gap programmatically (network.three_phase_readiness), fills it from explicit, configurable assumptions, records them innetwork_summary.jsonand in the report, and then verifies that the unbalanced model reproduces the balanced one for a deliberately symmetric operating point. It never falls back to a balanced solution while labelling the result three-phase.
| key | strategy | cos φ(P) | Q(U) | OLTC | curtailment |
|---|---|---|---|---|---|
no_control |
A: unity power factor, static tap | – | – | – | – |
cosphi_p |
B: cos φ(P) characteristic | ✓ | – | – | – |
qu |
C: Q(U) droop with deadband | – | ✓ | – | – |
oltc |
D: OLTC voltage-band control | – | – | ✓ | – |
fixed_curtailment |
E: fixed local feed-in cap | – | – | – | fixed |
prop_curtailment |
F: proportional coordinated | – | – | – | proportional |
sens_curtailment |
G: sensitivity-based (LP) | – | – | – | sensitivity |
fair_curtailment |
H: fairness-aware (LP) | – | – | – | fairness |
coordinated |
I: Q(U) + OLTC + sensitivity LP | – | ✓ | ✓ | sensitivity |
Every strategy runs the same deterministic controller sequence — update uncertain load
and available PV, autonomous inverter control, OLTC, evaluate remaining violations,
coordinated curtailment, re-run the nonlinear AC power flow, evaluate final metrics — so a
comparison is never confounded by controller ordering. Inverter capability
(
probabilistic-pv-hosting-capacity/
├── configs/
│ ├── demo.yaml # reduced sample counts, short live run
│ ├── research.yaml # larger sample counts, three critical days
│ └── sensitivity.yaml # alternative assumptions (never merged with the above)
├── src/probabilistic_pv_hosting_capacity/
│ ├── config.py # every scientific assumption, validated with pydantic
│ ├── network.py # SimBench selection, sequence-data gaps, distances
│ ├── profiles.py # profile inspection, critical-day selection, reduction
│ ├── uncertainty.py # correlated latent field + uncertain PV fleets
│ ├── phase_allocation.py # phase assignment, asymmetric conversion
│ ├── power_flow.py # templates, unified result, solver backends, benchmarks
│ ├── controls/ # base, no_control, cosphi_p, qu_control, oltc, curtailment
│ ├── violations.py # exceedances, severity, limiting-constraint classification
│ ├── risk.py # probabilities, confidence intervals, CVaR, bootstrap
│ ├── hosting_capacity.py # four definitions, adaptive search, isotonic curve
│ ├── sensitivity.py # finite differences, linear-model validation
│ ├── fairness.py # Jain, Gini, Lorenz, distributional metrics
│ ├── economics.py # illustrative cost model, Pareto frontier
│ ├── simulation.py # Monte Carlo engine, parallelism, caching
│ ├── study.py # campaign orchestration and output writing
│ ├── results.py # run directories and provenance metadata
│ ├── plotting.py # 18 publication figures (RWTH corporate design)
│ ├── reporting.py # study report + figure catalogue, generated from data
│ └── cli.py # `pv-hc` command-line interface
├── tests/ # focused unit tests + an end-to-end campaign test
├── outputs/<run_id>/ # all machine-readable results of a run
└── reports/ # study_report.md, figure_catalog.md
Requires uv and Python ≥ 3.12. No proprietary data and no commercial solver is used; every dependency is open source.
git clone https://github.com/skortmann/probabilistic-pv-hosting-capacity.git
cd probabilistic-pv-hosting-capacity
uv syncuv.lock pins the exact resolved versions. Do not install packages outside uv.
Optional but recommended. The figures use the rwth-latex style of
rwthplots, which typesets all labels with a
real LaTeX installation. On Debian/Ubuntu:
sudo apt install texlive-latex-extra texlive-fonts-recommended dvipng cm-super
Without LaTeX the same style is applied with matplotlib's mathtext instead, and the
substitution is recorded in figure_style.json and in the figure catalogue rather than
silently ignored.
# complete demonstration workflow (campaign + figures + reports) uv run pv-hc # inspect the candidate SimBench grids, the selected grid and its data gaps uv run pv-hc inspect-network --config configs/demo.yaml # validate the correlated uncertainty model against its configured targets uv run pv-hc validate-uncertainty --config configs/demo.yaml # run a campaign uv run pv-hc run --config configs/demo.yaml uv run pv-hc run --config configs/research.yaml uv run pv-hc run --config configs/sensitivity.yaml # override individual settings without editing the YAML uv run pv-hc run --config configs/demo.yaml --scenarios 100 --mode three_phase --workers 16 # re-analyse, re-plot or re-report an existing run without recomputing uv run pv-hc analyze --run-id <run_id> uv run pv-hc plot --run-id <run_id> uv run pv-hc report --run-id <run_id>
demo.yaml |
research.yaml |
|
|---|---|---|
| critical days | 1 | 3 |
| time resolution | 45 min (stride 3) | 30 min (stride 2) |
| Monte Carlo scenarios | 40 | 300 |
| statistically supported risk levels | 10 % | 10 %, 5 %, 1 % |
| capacity grid / bisection steps | 6 / 4 | 10 / 6 |
| power-flow mode | hybrid |
hybrid |
| indicative runtime (32 workers) | a few minutes | roughly an hour |
The demo is sized so that the 10 % risk level is genuinely supported
(
Each run writes everything needed to reproduce and re-analyse it:
outputs/<run_id>/
├── config_resolved.yaml # fully resolved configuration (all defaults expanded)
├── metadata.json # versions, git commit, seeds, hashes, timings, failures
├── network_summary.json # grid, distances, sequence-data assumptions
├── profile_inspection.json # resolution, units, naming, element assignment
├── uncertainty_validation.json # target vs empirical correlation structure
├── mode_consistency.json # balanced vs three-phase verification
├── scenario_parameters.parquet # sampled PV fleet per scenario (full traceability)
├── scenario_results.parquet # every scenario evaluation, all metrics
├── risk_curve.parquet # risk and severity per capacity and strategy
├── hosting_capacity_summary.csv # every definition, with support flags
├── strategy_summary.csv # technical, economic and fairness key figures
├── sensitivity_results.parquet # per-bus sensitivities and distances
├── fairness_results.csv # Jain, Gini, distributional metrics
├── limiting_constraints.csv # share of failures per constraint
├── hybrid_comparison.csv # matched balanced vs three-phase scenarios
├── voltage_quantiles.csv # per-step ensemble quantiles
├── convergence.csv # sample-size study
├── benchmark.csv # measured per-solve cost of the solver options
├── failures.csv # every failed operating point with its inputs
├── figures/ # 18 figures as PNG (300 dpi) and PDF
├── figure_data/ # the plotted data of every figure as CSV
└── trajectories/ # detailed time series of representative scenarios
| # | figure | question it answers |
|---|---|---|
| 1 | Network topology and uncertain PV allocation | Why can equal aggregate capacity have different impact? |
| 2 | Example correlated uncertainty scenarios | Is the stochastic model coherent rather than noise? |
| 3 | Correlation-model validation | Does the generator reproduce the intended correlations? |
| 4 | Risk curve | Main result: violation probability vs installed capacity |
| 5 | Violation-severity curves | Do equal probabilities hide different consequences? |
| 6 | Limiting-constraint decomposition | What physically limits the hosting capacity? |
| 7 | Voltage quantile fan chart | When is the voltage risk greatest? |
| 8 | Three-phase voltage and VUF heatmap | Which phase-specific effects does balancing hide? |
| 9 | Balanced versus three-phase comparison | How large is the balanced-screening error? |
| 10 | Control-strategy comparison | What does each strategy cost technically? |
| 11 | Hosting capacity versus curtailment | Is a little curtailment disproportionately effective? |
| 12 | Techno-economic Pareto frontier | Which strategies are non-dominated? |
| 13 | Curtailment fairness | Are the same prosumers repeatedly disadvantaged? |
| 14 | Spatial sensitivity map | Why are certain buses prioritised? |
| 15 | Electrical distance versus sensitivity | Is distance a sufficient proxy? |
| 16 | Monte Carlo convergence | Is the estimate stable in the sample size? |
| 17 | Representative critical scenario | How do the controls act over the day? |
| 18 | Graphical abstract | Overview of the whole workflow |
reports/figure_catalog.md documents each figure's file name, scientific question,
variables, caveats and a data-derived interpretation. The plotted data of every figure is
stored as CSV next to it.
The findings of a specific run are written to reports/study_report.md — the executive
summary, the hypothesis verdicts with their criteria, and the limiting-constraint
decomposition. They are deliberately not duplicated here, because they depend on the
configuration, the selected grid and the sample size, and a README cannot be regenerated
from a run.
Structural results that hold across the configurations shipped here:
- The selected rural SimBench LV feeder is transformer-thermally limited before the EN 50160 voltage band binds, so voltage-only measures (Q(U), OLTC) cannot raise its hosting capacity much, while curtailment can. Under the stricter 3 % voltage-rise planning criterion — evaluated in parallel and reported separately — voltage becomes co-limiting. The limiting constraint is a property of the grid, not a general result.
- The OLTC lowers voltages and therefore raises currents: on a thermally limited feeder it can reduce rather than increase the hosting capacity.
- Sensitivity-based and proportional curtailment need almost the same total energy on this feeder but distribute it very differently (Gini coefficients differ by an order of magnitude), and the fairness-aware policy restores near-equality at a small efficiency penalty.
- The unbalanced solution gives systematically higher maximum voltages and transformer loadings than the balanced screening, i.e. balanced screening is optimistic here.
Stated in full in reports/study_report.md, section 16. In brief: synthetic uncertainty
distributions; no measured household-level data; dependence on the selected SimBench grid
and time periods; assumed PV orientation and phase-connection rules; assumed
zero-sequence parameters (SimBench provides none); quasi-static controllers without
dynamics; finite Monte Carlo sample error; linearisation error of the coordinated
curtailment; illustrative economic coefficients; no protection, harmonic or
electromagnetic analysis; and a material difference between planning and operational
voltage limits.
- All randomness derives from one configurable seed through named, collision-free streams; common random numbers are used across strategies and capacity levels, so strategy comparisons are paired.
- Results are independent of the number of workers (verified by a test).
- Every run stores its resolved configuration, its configuration hash, the git commit, the Python and dependency versions, the seeds, the sample counts, the failure counts and the stage timings.
- Failed operating points are retained with their inputs in
failures.csvand are never dropped from the probability denominator; the conservative handling (non-convergence = violation) is the primary metric and the optimistic alternative is reported alongside it. recycle— reuse of pandapower's internal branch model, the dominant speed-up for long time series — is verified against freshly built solutions at run start and disabled automatically if it disagrees.
Measured per balanced solve on the default 97-bus feeder (written to benchmark.csv on
every run, so the numbers can be checked on any machine):
| configuration | per solve |
|---|---|
| pandapower solver, no branch-model reuse | 20–33 ms |
| lightsim2grid solver, no branch-model reuse | 20–32 ms |
lightsim2grid + recycle |
6–9 ms (×ばつ faster) |
runpp_3ph (unbalanced) |
≈ 33 ms |
pandapower 3.5.4 accepts lightsim2grid (default "auto", so it is active once the
package is installed) and its solution agrees with pandapower's own Newton–Raphson to
about
uv run ruff format . uv run ruff check . uv run mypy src uv run pytest --cov # add -m "not slow" to skip the end-to-end campaign
See CITATION.cff for this software. If you use the results, please also
cite the paper this work builds on:
@inproceedings{kortmann2021hosting, author = {Kortmann, Steffen and Han, Xuejiao and Schwarz, Marius and Hug, Gabriela}, title = {From a distributing to a generating network: Assessing PV hosting capacity under uncertainty in distribution grids}, booktitle = {PESS 2021; Power and Energy Student Summit}, year = {2021}, pages = {1--6}, url = {https://ieeexplore.ieee.org/document/9735254}, }
Built on pandapower and
SimBench — the SimBench benchmark grids and profiles are the
open data basis of this study. The balanced power flow is accelerated with
lightsim2grid; the figures use the RWTH Aachen
corporate-design style sheets from
rwthplots (RWTH-IAEW).
MIT — see LICENSE.