Changelog¶
Format: Keep a Changelog. Versioning: Semantic Versioning.
[Unreleased]¶
Added¶
- AC Optimal Power Flow (ACOPF) formulations for operational dispatch
- Four formulations: SOC relaxation (W-space), QC relaxation (McCormick envelopes), Polar NLP (exact V-θ), Rectangular NLP (exact e-f)
- All formulations use Ipopt solver; SDP mode requires MOSEK (not included)
- Reactive power balance with Q slack variables and configurable penalty
- Apparent power (MVA) line limits:
P² + Q² ≤ cap² - Reactive generation limits estimated from power factor when Q limits not specified
- KCL scaling in MW/MVAr for numerical robustness
- Flat voltage start values for NLP convergence
- Configurable parameters: voltage limits, power factors, reactance clamp, tap ratio bounds, Q slack penalty
- Julia:
setup_acopf!()intransmission_acopf.jl, 5 formulation dispatch types - Python:
ACPowerFlowConfigextended with 6 new fields (load_power_factor,q_slack_penalty,min_reactance_pu,tap_ratio_min,tap_ratio_max,q_min_ratio) - GUI: Power flow mode selector in System Settings panel with conditional AC parameter visibility
- Native AC power flow bridge (
NativeACBridge) — replaces pandapower for AC power flow and N-1 contingency analysis- Uses the Julia Newton-Raphson solver (
transmission_ac.jl) directly from the GUI, eliminating the pandapower dependency for AC analysis - New
GuiACPowerFlowInputstruct andsolve_gui_ac_power_flow()Julia entry point accept flat arrays (noPowerSystemInputrequired) - Drop-in replacement for
PandapowerBridgevia duck-typed interface:run_power_flow(),rerun_power_flow(),set_element_in_service() - Pandapower retained only for IEC 60909 short-circuit analysis (
calc_sc()) - Shared result types (
ACPowerFlowResult,ShortCircuitResult) extracted toac_types.pyfor backend-agnostic consumption - GUI automatically prefers native bridge; falls back to pandapower if Julia is not available
- Uses the Julia Newton-Raphson solver (
- Enhanced N-1 security analysis — 10 improvements to contingency analysis
- PTDF/LODF-based fast contingency evaluation: O(1) per line outage using precomputed sensitivity matrices
- Performance Index (PI) screening: rank and filter contingencies before detailed analysis
- Transformer contingencies: treated as branches in DC power flow with impedance-based modeling
- Battery contingencies: discharging batteries modeled as generation loss with droop redistribution
- Droop-based generation redistribution: participation factors based on 1/R × P_rated, consistent with frequency analysis
- N-1-1 (N-k) sequential analysis: second contingency evaluated on the stressed post-first-contingency state
- Integrated N-1 assessment (
IntegratedN1Analyzer): combines thermal, frequency, and voltage analysis with composite severity scoring - Security-Constrained OPF (SCOPF) in Julia: iterative constraint addition using LODF, adds only binding contingencies
- Corrective N-1 actions: post-contingency battery and generator response modeled as alternative to preventive reserve
- N-1 results in HDF5: generation reserve duals, transmission SCOPF duals, binding contingency list, security cost
- New config fields:
scopf_enabled,scopf_max_iterations,scopf_violation_tolerance,corrective_enabled - New Python modules:
n1_assessment.py(integrated analyzer), enhancedcontingency.py(PTDF/LODF, screening, batteries, transformers, N-1-1) - New Julia functions:
add_scopf_constraints!(),_build_ptdf_matrix(),_build_lodf_matrix(),_add_corrective_gen_n1!()
- Built-in documentation viewer (Help > Documentation, F1 shortcut)
- Integrated MkDocs viewer accessible from the GUI menu bar
- Opens the full documentation site in a docked browser panel
- F1 keyboard shortcut for quick access from any screen
- Bidding/offer curves for generators and batteries
- Support for flat, linear, stepwise, and exponential cost curves
- Piecewise-linear (PWL) cost decomposition in operational dispatch and capacity expansion
- Generator output decomposed into segments with non-decreasing marginal costs (convex)
- Battery discharge cost curves with same formulation
- New data structures:
CostCurveBlock(fraction + price pair),CostCurveConfig(curve_type + parameters),normalize_cost_curve()(converts any curve to stepwise blocks) - New config fields:
fuel_cost_curveonGeneratorConfigandTechnologyConfig,discharge_cost_curveonBatteryConfig - GUI widgets for configuring cost curves in generator and battery forms (dropdown with Flat/Linear/Stepwise/Exponential modes)
- Backward compatible: existing flat
fuel_costfield continues to work unchanged
- Per-technology investment model for capacity expansion
TechnologyConfig: candidate generation technology with per-node investment costs, limits, fuel type, and availabilityBatteryTechnologyConfig: candidate storage technology with power/energy investment, duration limits, and degradation- Master problem optimizes technology-level investments instead of per-generator investments
- Virtual generators and batteries created from cumulative technology investments for operational dispatch
- Config sections:
technologiesandbattery_technologiesin YAML - Julia structs:
TechnologyConfig,BatteryTechnologyConfigintypes.jl
- Plugin system (QGIS/KiCad-style directory-based discovery)
ESFEXPluginbase class with lifecycle, runner, Julia, CLI, and GUI hooksPluginManagersingleton: discovery from~/.esfex/plugins/, project-local,$ESFEX_PLUGIN_PATH- Plugin loading via
importlib.util.spec_from_file_location(no pip/PyPI required) - Enable/disable persistence via
~/.esfex/plugins.json - Install from git (
esfex plugin install --git <url>) or ZIP (--zip <path>) - CLI sub-commands:
esfex plugin list|install|uninstall|enable|disable - 7 runner hooks:
pre_simulation,post_demand_loaded,pre/post_master_problem,pre/post_year,post_simulation - Julia runtime overlays: plugins can inject
.jlmodules viainclude()without modifying core - GUI extensions: tree categories, property forms, toolbar actions, menu items, result variables, map layers, translations
- GUI Plugins menu with "Manage Plugins..." dialog; loaded plugins register their own menu actions
PluginsDialog: table view of all discovered plugins, install from ZIP/Git, uninstall, enable/disable, open plugins folderplugins: dict[str, Any]field onESFEXConfigfor per-plugin configuration- Safety: all hook calls wrapped in
try/except--- broken plugins never crash the core - Security: ZIP Slip protection, git hooks disabled during clone, URL scheme validation
- MGA / SPORES near-optimal alternative exploration — two distinct methods sharing the same cost-slack envelope
- Classical MGA [8] (DeCarolis 2011): Hop-Skip-Jump diversity loop. Generates \(K\) alternatives, each maximising a single diversity objective weighted by the frequency score
1 − 2·freqthat penalises previously-selected investment variables. Configuration:master_problem.mga.method = "mga",num_alternatives,slack_fraction,investment_threshold. - SPORES [7] (Lombardi et al. 2020): one alternative per declared objective. Four canonical objectives ship out of the box:
min_total_build,max_tech_equity,max_regional_equity,evolutionary_dist. Configuration:master_problem.mga.method = "spores",objectives = [...]; the alternative count equalslen(objectives). - LP-compatible objectives throughout — no binary variables introduced; the L1 norm in
evolutionary_distis linearised with positive / negative deviation aux variables. - JuMP model reuse: per-objective aux vars / constraints are stashed under
model[:_spores_objective_aux]and deleted before the next objective is installed, so the model never accumulates dead variables across an SPORES sweep. - Julia:
run_mga_spores()(classical MGA) andrun_spores()(SPORES sweep) inmga.jl, with dispatcherapply_spores_objective!(model, vars, input, objective; …). Per-objective installers:set_spores_objective!()(HSJ),set_min_build_objective!(),set_tech_equity_objective!(),set_regional_equity_objective!(),set_evolutionary_distance_objective!(). - Python:
MGAAdapterdispatches onconfig.method; the result dict is uniform across both methods, with a per-alternativeobjectivetag ("cost_optimal","hsj_diversity", or a SPORES objective name). - Schema:
SporesObjectiveenum + cross-field validator onMGAConfigthat rejectsmethod='spores'without objectives andmethod='mga'with objectives populated. - GUI: method combo and SPORES-objective checklist in Global Settings; the inactive method's fields hide entirely (rather than just greying out) to keep the form compact.
- HDF5 export:
/mga.attrs["method"]and/mga.attrs["objectives"]at the group root; per-alternativeattrs["objective"]on every/mga/alternative_N/. Pre-existing fields (alternative_id,is_optimal,cost,diversity_objective) preserved. - Viewer:
_MGA_OBJECTIVE_COLORSpalette and aware reading in_build_mga_bundle. Robust Frontier and Alternative Map colour markers by objective; Cluster Tree's middle annotation ring swaps RE-peak for objective when method is SPORES. Legacy MGA result files render unchanged via back-compat defaults.
- Classical MGA [8] (DeCarolis 2011): Hop-Skip-Jump diversity loop. Generates \(K\) alternatives, each maximising a single diversity objective weighted by the frequency score
- Battery cyclic SOC constraint
- End-of-day SOC must return to initial SOC value:
bat_soc[b, n, hours] == initial_soc - Prevents batteries from acting as infinite energy sources across rolling horizon windows
- Applied in
add_day_operational_constraints!in the Julia backend
- End-of-day SOC must return to initial SOC value:
- Curtailment limit constraint
- Maximum curtailment capped at
max_curtailment_ratio(default 5%) of total renewable generation - Constraint-based approach (not cost-based) to incentivize storage investment
- Julia:
sum(curtailment) <= ratio * sum(renewable_gen)
- Maximum curtailment capped at
- Virtual generator/battery concept for operational dispatch
- Technology investments from the master problem are materialized as virtual units for hourly dispatch
_build_config_from_cumulative()creates virtualGeneratorConfig/BatteryConfigdictsPowerSystemAdapter._create_input()creates Julia objects for virtual units fromunits_config- Virtual units appear after original units in HDF5 export (name ordering matches adapter)
_rebuild_unit_names()ensures consistent naming between adapter and exporter
- Reservoir hydroelectric modeling as an extension to generators
- Optional reservoir dynamics: water level tracking, inflows, spillage, evaporation
- Pumped-storage hydro: pump-back power as demand-side load in power balance
- Reservoir capacity investment: optimizer can expand reservoir storage
- Cyclic constraint: end-of-horizon level matches initial (within tolerance)
- Inflow profile caching for performance (same pattern as availability profiles)
- Julia:
add_reservoir_constraints!inpower_system.jl, investment inmaster_problem.jl - Python: 12 fields on
GeneratorConfig, inflow preloading inrunner.py - GUI: Reservoir section in generator form with conditional visibility
- HDF5 export:
reservoir_level,reservoir_spillage,reservoir_pumpdatasets
- Availability profile caching for performance
_preload_availability_profiles()loads ALL availability files ONCE at startupPowerSystemAdapterreceivesavailability_cacheparameter_create_input()uses cache instead of loading files per window- Eliminates thousands of redundant file reads (208 windows x 10 files x 25 years)
- Keyboard shortcuts dialog (Help > Keyboard Shortcuts)
- Displays all available keyboard shortcuts organized by category
- Includes map navigation, element operations, and menu shortcuts
- About ESFEX dialog (Help > About ESFEX)
- Displays version, build information, and credits
- Links to documentation and issue tracker
- Inter-system DC-OPF transmission model replacing NTC with proper DC power flow
- Bidirectional flow variable with direction decomposition:
pf = fp - fn - PWL loss approximation for inter-system links with configurable segments
- Half-loss split on KCL injections at boundary nodes
- Linear loss fallback when R/X not provided
- Independent voltage references per system (HVDC-like model)
- Configuration:
reactance_pu,resistance_puinSystemLinkConfig - Configuration:
inter_system_loss_segmentsinMetaNetworkConfig
- Bidirectional flow variable with direction decomposition:
- Piecewise linear (PWL) transmission loss model approximating quadratic I^2R losses
- Three loss modes:
none(lossless),linear(legacy constant factor),pwl(default) - Half-loss split formulation for balanced loss allocation at bus endpoints
- Complete documentation site with MkDocs Material
- Mathematical formulation reference for all constraint families
- API reference for all Python and Julia modules
- GUI documentation covering all editor features
- 9 tutorials covering basic to advanced usage
- Configuration reference with all YAML fields
- HDF5 output schema reference
- Constraint catalog with all Julia constraint labels
- Glossary of domain terminology
- Comprehensive documentation expansion across all reference pages
- Availability Generator bundled plugin for generating solar/wind availability profiles from weather reanalysis data (Open-Meteo, NASA POWER, ERA5)
- Financial Analysis Workflow — post-optimization financial assessment of energy system investments [48][64]
- NPV decomposition, IRR/MIRR, WACC, DSCR, LCOE/LCOS/VALCOE, pro-forma cash flows, and payback analysis
- Per-technology financial breakdown: capital cost, revenue, capacity factor, LCOE, ROI
- Sensitivity analysis (one-at-a-time parameter sweeps) with tornado and spider diagrams
- Monte Carlo simulation with NPV/IRR probability distributions, Value-at-Risk (VaR), and Conditional VaR
- Python engine (
esfex.models.financial_analysis) usable independently of the GUI for scripting and batch analysis - 8-step GUI wizard accessible via Workflows > Financial Analysis
- Granular cost decomposition from the optimizer (
CostBreakdownstruct with 27 cost components) exported to HDF5/cost_breakdown/year_YYYY/
Fixed¶
- DC power flow self-loop transfer bug --- transfer variables
trans_(i,i)created free energy; self-loops now skipped withi == j && continueinmaster_problem.jl - Free generation bug ($50 objective) --- generator variables with
rated_power=0andinvest_max=0had no upper bound and zero cost; fixed by constraining output to zero when inactive - Investments not applied to operational dispatch --- master problem investments were calculated but not used in operational windows; fixed key parsing (
split("_")[1]was wrong) and adapter integration - Curtailment in DC power flow --- removed
curtailmentfrom KCL equation (curtailment is energy never generated, not a power balance term) - Penalty coefficient magnitude ---
fre_penaltywas 600M instead of 600, causing solver stall; now reads from config (sys.penalties) - Battery acting as infinite generator --- missing cyclic SOC constraint allowed batteries to generate energy indefinitely; fixed with
bat_soc[b, n, hours] == initial_soc - Virtual generator HDF5 export --- virtual units (from technology investments) were invisible in HDF5 output; added
_rebuild_unit_names()to match adapter ordering - Availability resolution mismatch --- operational windows sliced availability as raw hours instead of resolution-adjusted blocks; fixed by passing
ESFEXConfiginstead ofSystemConfigto adapter
Changed¶
- Inter-system transmission now uses DC-OPF formulation instead of NTC-based coupling hack
- Master problem inter-system constraints now model physical power flow with losses
- External injections passed to operational dispatch for proper multi-system coordination
[0.1.3] --- 2026-06-05¶
Fixed¶
- Grid Builder demand-forecast crash: the forecast step's worker threads
(country detection, World Bank/ERA5 fetch, ML forecast) updated Qt widgets
directly, violating Qt's main-thread-only GUI rule and segfaulting
(
Cannot create children for a parent that is in a different thread). The heavy work still runs off the GUI thread, but every widget update is now marshalled to the main thread via a queued signal.
[0.1.2] --- 2026-06-05¶
Added¶
- Benders decomposition as an optional master-problem solver
(
master_problem.solver_method: monolithic | benders). The investment-only master withθ[y]recourse variables plus per-representative-day dispatch subproblems and optimality cuts is beneficial for very large problems. Configurable viabenders_max_iterations,benders_tolerance,benders_lol_penalty_cap, and selectable from the Studio's master-problem settings. Monolithic remains the default. - OpenSSF Best Practices badge.
Fixed¶
- Grid Builder bus-distribution step no longer freezes the UI on
whole-country footprint sets: building-footprint classification and the
nearest-bus assignment run in a background thread, with a vectorised
classifier, single centroid pass, and
np.bincountaccumulation.
[0.1.1] --- 2026-06-04¶
Fixed¶
- Grid Builder demand forecast crash: the per-node demand forecast read
latitude/longitudeon grid nodes, butGuiNodeexposes its geographic position ascentroid_lat/centroid_lng. Running the forecast raisedAttributeError: 'GuiNode' object has no attribute 'latitude'. - Fuel-entry-point duplication crash: duplicating a fuel entry point nudged
coordinate.latitude/.longitude, butGeoPointuseslat/lng, so the action raised anAttributeError. - GeoJSON fuel-entry import: fuel entry points were constructed with invalid
max_import_rate/import_costkeyword arguments; these are now passed through thefuel_paramsmapping. - GeoJSON node import: nodes are now created from
Pointfeatures and snapped to the nearest existing node by great-circle (haversine) distance to the centroid, instead of always attaching to the first node.
Changed¶
- Native Julia test suite expanded from a handful of smoke tests to full
unit and end-to-end model-solve coverage; Julia core coverage is now reported
to Codecov under a dedicated
juliaflag.
[0.1.0] --- 2025-XX-XX¶
Added¶
-
Core Simulation Engine
- Capacity expansion planning (Master Problem) with NPV minimization
- Operational dispatch with rolling horizon (48h windows, 6h overlap)
- Economic dispatch (LP) and unit commitment (MIP) modes
- Age-based retirement: existing and invested units
- Multi-year simulation with demand growth
-
Power System Modeling
- Generator constraints: capacity, ramp rates, min up/down time, efficiency curves
- Battery constraints: SOC dynamics, charge/discharge limits, cyclic SOC, spillage
- DC power flow with Kirchhoff's laws (KCL/KVL)
- Transmission line capacity constraints with investment
- Curtailment limits (5% of RE generation by default)
- RE penetration targets with annual progression
- CO2 budget constraints
- System inertia requirements
- N-1 security (transmission and generation contingencies)
- Sectoral demand with criticality-based load shedding
- Demand flexibility and shifting
-
Advanced Features
- EV fleet modeling with S-curve adoption and V2G
- Rooftop solar with adoption scenarios
- Primary energy supply chain (import, storage, transport)
- Electrolyzers (PEM, Alkaline, SOE)
- Multi-system coordination with inter-system links
- Stochastic capacity expansion with scenario multipliers
- Sobol sensitivity analysis (via SALib)
-
Network Equipment
- AC/DC converters (VSC and LCC)
- Frequency converters
- Transformers with impedance modeling
- Bus-level modeling with voltage/frequency attributes
- Development zones for technology deployment
-
Results & Export
- HDF5 output with full time series
- Derived metrics: LCOE, VALLCOE, capacity factor
- Electricity price decomposition (energy, congestion)
- Technology selling prices and revenue analysis
- CSV, Excel, and JSON export formats
-
GUI Editor
- PySide6 + Leaflet.js interactive map editor
- Element tree with 19 categories per system
- Context-sensitive property forms for all element types
- Polyline trace for transmission lines and fuel routes
- Magnetic snapping for equipment placement
- Multi-system management
- Inter-system link editor
- GeoJSON import with feature-to-element mapping
- Light, Dark, and Twilight themes
- Python console and script editor
- Analysis wizards: Solar PV, Wind, OTEC, Rooftop Solar
-
Configuration
- Pydantic v2 schema validation
- YAML configuration format
- CLI with run, validate, export, editor, info commands
-
Solver Support
- HiGHS (default, open-source)
- Gurobi (commercial, optional)
- CPLEX (commercial, optional)
- CBC (open-source, optional)
- GLPK (open-source, optional)
-
Python-Julia Bridge
- juliacall integration for seamless optimization calls
- System image support for faster startup
- Availability profile caching for performance