Skip to main content

nexus-energy

Energy-system optimisation in Python — that also answers backwards.

Build and optimise an energy-system digital twin the way you do today, at exact parity with PyPSA and GenX. Then ask the question they can't: which of my inputs is wrong?

License: MIT Python Solver core

Website · User guide · Benchmarks · Solver core


Who this is for

Engineers and researchers who build energy-system models in Python — capacity expansion, economic dispatch, unit commitment, optimal power flow — and who need the model to agree with a system that actually exists.

Why it exists

Every production energy tool solves the problem forward: given these costs and efficiencies, here is the optimal plan. That is well served already.

The expensive daily question is the reverse. Your twin's output does not match the real system — which input is wrong, and by how much? Today that is answered by brute force: perturb a parameter, re-solve, repeat. Morris, Sobol and MGA sweeps cost hours to days, scale with every parameter, and return a confident number even when the data cannot identify it.

nexus-energy differentiates through the optimisation itself, so the answer comes from the model's own structure — in a handful of solves, and it says so when the data is silent.

Install

pip install nexus-energy

This pulls in nexus-opt, the Rust solver core, automatically. No separate step, no Rust toolchain. Python 3.9+.

Not yet on PyPI. The wheel matrix is built and verified but publication is pending. Until then install from source — see Development.

Sixty seconds

import nexus_energy as ne

sys = ne.EnergySystem("my_system")
elec = sys.add_bus("elec", carrier="electricity")

sys.add_generator("solar", bus=elec, capacity=500, marginal_cost=0)
sys.add_generator("gas",   bus=elec, capacity=200, marginal_cost=50)
sys.add_load("demand", bus=elec, amount=300)

result = sys.optimise()
print(result.status, result.total_cost)   # optimal 0.0
print(result.generator_dispatch)          # {'solar': [300.], 'gas': [0.]}

And the part that is new — recovering a hidden CO₂ price from observed dispatch:

from nexus_energy.pypsa_compat import from_pypsa
from nexus_energy.diff_bridge import fit_co2_price

system = from_pypsa(network, line_model="transport")
fit = fit_co2_price(system, observed_dispatch)

print(fit.price, fit.n_solves, fit.converged)

fit_co2_price runs in two stages — a coarse forward-only bracket (dispatch is piecewise-linear in price, so the loss has flat pieces a pure gradient method is stuck on) followed by safeguarded Gauss–Newton with a bisection fallback.

What it covers

Area Capability
Network physics DC-OPF, polar AC-OPF, SOCP conic relaxations, HVDC, N-1 security
Dispatch Unit commitment (tighter formulation by default), start-up/shutdown costs, ramping, must-run, spinning and regulation reserves
Storage Self-discharge, cyclic SoC, asymmetric charge/discharge efficiency, simultaneous charge/discharge bans, cascade hydro, EV and V2G
Policy Zonal/global/pooled CO₂ caps, RPS, clean energy standards, ITC/PTC, hourly matching
Temporal Time-series aggregation, representative periods, rolling horizon
At scale Benders and temporal decomposition, stochastic scenarios, CVaR, robust optimisation
Operations Model predictive control, auto-calibration from telemetry
Differentiable Analytic KKT gradients, design gradients (∂cost/∂capacity), inverse calibration

The component library

223 components across 15 sectors, each at several fidelity levels — fidelity being how much physics the component carries. The same LFP cell is available as:

Level Model What it adds
F0a Round-trip efficiency curve One lookup vs C-rate. Cheapest.
F1a State of charge Energy in, energy out, SoC over time
F1b + thermal Cell temperature moves the efficiency
F1c + degradation Capacity fade over cycles
F2a Equivalent circuit (1RC) Real voltage dynamics
F2d Single-particle model Electrochemical detail

Pick the cheapest level that still answers the question — per component, not per study. A capacity screen can run everything at F0 while the one asset under investigation runs at F2.

Sector split: thermal 41 · power electronics 32 · batteries 26 · solar 18 · hydrogen 17 · conventional 13 · hydro & marine 12 · biomass 11 · carbon capture 11 · gas systems 9 · thermoelectric 8 · desalination 7 · wind 6 · mechanical storage 6 · geothermal 6.

F0–F2 are built for all 223. F3–F6 (distributed physics, AI surrogates, PINNs) are scaffolded but not yet implemented.

Coming from PyPSA

from_pypsa(network) converts a PyPSA Network into an EnergySystem. It is a one-way adapter that reads the network's dataframes — the library itself does not depend on PyPSA and never calls it to solve. It exists so a benchmark can put the identical network in front of both solvers, and so an existing PyPSA workflow has an on-ramp.

from nexus_energy.pypsa_compat import from_pypsa

system = from_pypsa(n)                            # AC lines auto-route to DC-OPF
system = from_pypsa(n, line_model="transport")    # required by the diff layer

Benchmarks

Full detail in COMPARISON_SCORECARD.md; stored results under benchmarks/results/, so every number can be re-run rather than taken on trust.

Case Objective vs reference Wall clock
PyPSA-Eur capacity expansion (10 bus, 2190 h, real profiles) −0.000 % exact parity 233 s vs 810 s — 3.47× faster
GenX 1_three_zones_ucommit2 −0.02 % exact 7.7 s vs 36.1 s — 4.7× faster
pandapower AC-OPF case9 / case14 +0.0007 % / +0.0792 % 60.5× / 42.2×
PowerModels.jl SOCWR, 3-bus radial 3.67e-5 7.57×
CINDER LP parity 147 s vs 190 s — 1.3× faster
CINDER MILP MIP gap 0.82 % 330 s vs 190 s — 1.7× slower

Both solvers see the identical network — that is what from_pypsa is for. Two real bugs in this library were found because the inputs were held identical (a static scalar p_max_pu being ignored, and cyclic storage being over-pinned).

Known open problem

Two GenX cases (rate_co2 at −42.7 %, mincapreq at −3.90 %) look like large wins and are not. Feeding the capacity nexus chose back into a real GenX solve showed GenX's own cost for the nexus solution (5.823e9) essentially matches GenX's optimum (5.808e9), while nexus reports 5.582e9 — a ~4.3 % OPEX under-count bug here, not a cheaper optimum. Lead suspect is transmission-loss modelling (GenX piecewise-linear vs a linear loss=%).

The pattern: exact match when the build is determined, opex under-count when multi-zone renewables are dispatched over transmission. The exact-parity rows above are unaffected — none of them involve that case.

What it does not claim

  • Not dynamic or EMT simulation. No transients, no swing equation. Time is snapshots and rolling horizons coupled by algebraic constraints — a different category from Simulink and Modelica.
  • No integer (UC-MILP) differentiability. Future work, not a claim.
  • Calibration is LP/QP class. A small ridge term, required to make the gradients well defined, shifts economics by roughly 1–7 percentage points and is disclosed per result.
  • The speed headline is forward-only — the calibration solve uses a denser path.

Development

git clone https://github.com/VishalRam24/nexus-energy
cd nexus-energy
uv sync
uv run pytest

Benchmark scripts that compare against pandapower, PowerModels.jl or GenX expect those reference installations outside this repo and skip automatically when they are absent. Point them elsewhere with NEXUS_PANDAPOWER_DIR / NEXUS_POWERMODELS_DIR.

Third-party solvers and frameworks appear only as benchmark comparison rows — they are never wrapped inside the library.

Licence

MIT — see LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nexus_energy-0.1.0.tar.gz (11.4 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nexus_energy-0.1.0-py3-none-any.whl (292.3 kB view details)

Uploaded Python 3

File details

Details for the file nexus_energy-0.1.0.tar.gz.

File metadata

  • Download URL: nexus_energy-0.1.0.tar.gz
  • Upload date:
  • Size: 11.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for nexus_energy-0.1.0.tar.gz
Algorithm Hash digest
SHA256 daa1dec20009356ea7f59814837e47f0233b8126b063f39c6227971200b0aef5
MD5 02869004158cdaa82a5caedc0e3d6c10
BLAKE2b-256 bceee800a28b19758e464521760ccb66f2a8da38bbd6abd76fde7d8b16ad1f72

See more details on using hashes here.

Provenance

The following attestation bundles were made for nexus_energy-0.1.0.tar.gz:

Publisher: publish.yml on VishalRam24/nexus-energy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nexus_energy-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: nexus_energy-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 292.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for nexus_energy-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 67a484c30bc2efb580e5b2627a16b04d52452b08622121076dd19f06bbcbf22e
MD5 697d84258da4d065546a07f0d70118f8
BLAKE2b-256 954564379bf900b231bade90ca5380dd88e70f55696658cf3373cfecc0275674

See more details on using hashes here.

Provenance

The following attestation bundles were made for nexus_energy-0.1.0-py3-none-any.whl:

Publisher: publish.yml on VishalRam24/nexus-energy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page