esfex 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esfex/__init__.py +17 -0
- esfex/analysis/__init__.py +45 -0
- esfex/analysis/ac_contingency.py +366 -0
- esfex/analysis/ac_types.py +49 -0
- esfex/analysis/contingency.py +1651 -0
- esfex/analysis/frequency.py +370 -0
- esfex/analysis/n1_assessment.py +544 -0
- esfex/analysis/native_ac_bridge.py +549 -0
- esfex/analysis/pandapower_bridge.py +533 -0
- esfex/analysis/snapshot_builder.py +207 -0
- esfex/bridge/__init__.py +18 -0
- esfex/bridge/adapters.py +3814 -0
- esfex/bridge/converters.py +2003 -0
- esfex/bridge/julia_setup.py +340 -0
- esfex/bridge/topology_audit.py +345 -0
- esfex/cli.py +792 -0
- esfex/config/__init__.py +29 -0
- esfex/config/loader.py +636 -0
- esfex/config/schema.py +1926 -0
- esfex/config/solver.py +692 -0
- esfex/icons/battery.svg +7 -0
- esfex/icons/busbar.svg +4 -0
- esfex/icons/converter.svg +5 -0
- esfex/icons/development.svg +6 -0
- esfex/icons/electrolizer.svg +6 -0
- esfex/icons/esfex.png +0 -0
- esfex/icons/esfex.svg +224 -0
- esfex/icons/frequency.svg +6 -0
- esfex/icons/fuel_entry.svg +4 -0
- esfex/icons/fuel_storage.svg +6 -0
- esfex/icons/fuel_transport.svg +5 -0
- esfex/icons/generator.svg +5 -0
- esfex/icons/icon.svg +55 -0
- esfex/icons/power_line.svg +53 -0
- esfex/icons/results.svg +37 -0
- esfex/icons/risk.svg +51 -0
- esfex/icons/run.svg +52 -0
- esfex/icons/selection.svg +15 -0
- esfex/icons/sensibility.svg +32 -0
- esfex/icons/system.svg +59 -0
- esfex/icons/trafo.svg +73 -0
- esfex/icons/validate.svg +33 -0
- esfex/io/__init__.py +32 -0
- esfex/io/demand.py +500 -0
- esfex/io/exporter.py +514 -0
- esfex/julia/Manifest.toml +941 -0
- esfex/julia/Project.toml +37 -0
- esfex/julia/build_sysimage.jl +87 -0
- esfex/julia/precompile_workload.jl +210 -0
- esfex/julia/src/ESFEX.jl +390 -0
- esfex/julia/src/acopf_benchmark.jl +328 -0
- esfex/julia/src/dcopf_benchmark.jl +147 -0
- esfex/julia/src/electrolyzer.jl +247 -0
- esfex/julia/src/master_problem.jl +4611 -0
- esfex/julia/src/mga.jl +778 -0
- esfex/julia/src/power_system.jl +4162 -0
- esfex/julia/src/primary_energy.jl +1709 -0
- esfex/julia/src/transmission_ac.jl +762 -0
- esfex/julia/src/transmission_acopf.jl +1222 -0
- esfex/julia/src/transmission_dc.jl +820 -0
- esfex/julia/src/types.jl +2780 -0
- esfex/logging_config.py +217 -0
- esfex/models/__init__.py +71 -0
- esfex/models/adoption_models.py +524 -0
- esfex/models/climate_profiles.py +672 -0
- esfex/models/country_metadata.py +399 -0
- esfex/models/demand_dataset.py +387 -0
- esfex/models/demand_ml.py +296 -0
- esfex/models/demand_projection.py +628 -0
- esfex/models/demand_real_data.py +1755 -0
- esfex/models/demand_tft.py +534 -0
- esfex/models/demand_training.py +814 -0
- esfex/models/demand_zonal_fetchers.py +690 -0
- esfex/models/download_cmip6.py +149 -0
- esfex/models/download_cmip6_subdaily.py +261 -0
- esfex/models/ecvi_gridded.py +774 -0
- esfex/models/ev.py +406 -0
- esfex/models/ev_adoption.py +35 -0
- esfex/models/ev_analysis.py +35 -0
- esfex/models/financial_analysis.py +1718 -0
- esfex/models/hazard_assessment.py +3898 -0
- esfex/models/otec_models.py +275 -0
- esfex/models/pixel_features.py +217 -0
- esfex/models/solar_pv_models.py +535 -0
- esfex/models/solar_rooftop.py +363 -0
- esfex/models/tft_inference.py +466 -0
- esfex/models/tsam.py +282 -0
- esfex/models/wind_models.py +540 -0
- esfex/models/zone_centroids.py +243 -0
- esfex/paths.py +57 -0
- esfex/plugins/__init__.py +12 -0
- esfex/plugins/availability_generator/__init__.py +33 -0
- esfex/plugins/availability_generator/cli_commands.py +148 -0
- esfex/plugins/availability_generator/generator.py +325 -0
- esfex/plugins/availability_generator/grid_builder_hook.py +197 -0
- esfex/plugins/availability_generator/gui_dialog.py +587 -0
- esfex/plugins/availability_generator/otec_cf.py +212 -0
- esfex/plugins/availability_generator/plugin.json +8 -0
- esfex/plugins/availability_generator/solar_cf.py +322 -0
- esfex/plugins/availability_generator/synthetic_cf.py +178 -0
- esfex/plugins/availability_generator/wind_cf.py +403 -0
- esfex/plugins/manager.py +924 -0
- esfex/plugins/protocol.py +218 -0
- esfex/runner.py +7026 -0
- esfex/sensitivity/__init__.py +9 -0
- esfex/sensitivity/engine.py +501 -0
- esfex/sensitivity/lp_parser.py +618 -0
- esfex/sensitivity/worker.py +67 -0
- esfex/topology/__init__.py +49 -0
- esfex/topology/network_reducer.py +651 -0
- esfex/topology/reduction_map.py +160 -0
- esfex/topology/result_expander.py +276 -0
- esfex/topology/transformations.py +651 -0
- esfex/utils/__init__.py +51 -0
- esfex/utils/helpers.py +406 -0
- esfex/utils/paths.py +55 -0
- esfex/utils/temporal.py +337 -0
- esfex/visualization/__init__.py +66 -0
- esfex/visualization/app.py +126 -0
- esfex/visualization/bridge/__init__.py +0 -0
- esfex/visualization/bridge/channel.py +31 -0
- esfex/visualization/bridge/js_bridge.py +156 -0
- esfex/visualization/bridge/sld_bridge.py +51 -0
- esfex/visualization/data/__init__.py +0 -0
- esfex/visualization/data/auto_complete.py +681 -0
- esfex/visualization/data/connectivity_rules.py +117 -0
- esfex/visualization/data/default_colors.py +51 -0
- esfex/visualization/data/geo_asset_parser.py +947 -0
- esfex/visualization/data/geojson_importer.py +220 -0
- esfex/visualization/data/gui_model.py +2778 -0
- esfex/visualization/data/serializer.py +3522 -0
- esfex/visualization/data/undo.py +54 -0
- esfex/visualization/data/validation.py +5679 -0
- esfex/visualization/i18n.py +129 -0
- esfex/visualization/main_window.py +7425 -0
- esfex/visualization/map_widget.py +941 -0
- esfex/visualization/modern_widgets.py +370 -0
- esfex/visualization/panels/__init__.py +0 -0
- esfex/visualization/panels/_dialogs.py +37 -0
- esfex/visualization/panels/acdc_converter_form.py +421 -0
- esfex/visualization/panels/analysis_panel.py +533 -0
- esfex/visualization/panels/auto_complete_dialog.py +157 -0
- esfex/visualization/panels/battery_form.py +561 -0
- esfex/visualization/panels/bus_form.py +246 -0
- esfex/visualization/panels/collapse_button.py +215 -0
- esfex/visualization/panels/dashboard_loader.py +1895 -0
- esfex/visualization/panels/dashboard_view.py +224 -0
- esfex/visualization/panels/doc_viewer.py +691 -0
- esfex/visualization/panels/electrolyzer_form.py +329 -0
- esfex/visualization/panels/element_tree.py +1320 -0
- esfex/visualization/panels/ev_form.py +298 -0
- esfex/visualization/panels/freq_converter_form.py +405 -0
- esfex/visualization/panels/fuel_entry_form.py +273 -0
- esfex/visualization/panels/fuel_form.py +187 -0
- esfex/visualization/panels/fuel_route_form.py +376 -0
- esfex/visualization/panels/fuel_source_form.py +245 -0
- esfex/visualization/panels/fuel_storage_form.py +272 -0
- esfex/visualization/panels/generator_form.py +710 -0
- esfex/visualization/panels/global_settings_form.py +1421 -0
- esfex/visualization/panels/inter_system_link_form.py +473 -0
- esfex/visualization/panels/investment_form.py +358 -0
- esfex/visualization/panels/line_form.py +424 -0
- esfex/visualization/panels/multi_edit.py +133 -0
- esfex/visualization/panels/node_form.py +897 -0
- esfex/visualization/panels/parse_geo_asset_dialog.py +380 -0
- esfex/visualization/panels/plugins_dialog.py +352 -0
- esfex/visualization/panels/preferences_dialog.py +584 -0
- esfex/visualization/panels/properties.py +214 -0
- esfex/visualization/panels/python_console.py +400 -0
- esfex/visualization/panels/results_cache.py +149 -0
- esfex/visualization/panels/results_charts.py +10457 -0
- esfex/visualization/panels/results_charts_bokeh.py +1552 -0
- esfex/visualization/panels/results_charts_plotly.py +1690 -0
- esfex/visualization/panels/results_dialog.py +1588 -0
- esfex/visualization/panels/results_panel.py +2039 -0
- esfex/visualization/panels/rooftop_solar_form.py +273 -0
- esfex/visualization/panels/script_editor.py +469 -0
- esfex/visualization/panels/sensitivity_dialog.py +477 -0
- esfex/visualization/panels/simulation_dialog.py +204 -0
- esfex/visualization/panels/stochastic_form.py +475 -0
- esfex/visualization/panels/system_form.py +323 -0
- esfex/visualization/panels/technology_form.py +333 -0
- esfex/visualization/panels/toolbar.py +482 -0
- esfex/visualization/panels/transformer_form.py +337 -0
- esfex/visualization/panels/validation_dialog.py +819 -0
- esfex/visualization/panels/visual_style_widget.py +210 -0
- esfex/visualization/panels/word_wrap_header.py +102 -0
- esfex/visualization/panels/zone_form.py +439 -0
- esfex/visualization/preferences.py +237 -0
- esfex/visualization/pty_runner.py +248 -0
- esfex/visualization/resources/battery_heatmap.html +31 -0
- esfex/visualization/resources/battery_heatmap.js +259 -0
- esfex/visualization/resources/battery_operation.html +31 -0
- esfex/visualization/resources/battery_operation.js +153 -0
- esfex/visualization/resources/carbon_penalty.html +31 -0
- esfex/visualization/resources/carbon_penalty.js +186 -0
- esfex/visualization/resources/cash_flow.html +31 -0
- esfex/visualization/resources/cash_flow.js +306 -0
- esfex/visualization/resources/chart_theme.js +153 -0
- esfex/visualization/resources/custom_chart.html +31 -0
- esfex/visualization/resources/custom_chart.js +201 -0
- esfex/visualization/resources/d3.v7.min.js +2 -0
- esfex/visualization/resources/dashboard.html +270 -0
- esfex/visualization/resources/dashboard.js +759 -0
- esfex/visualization/resources/electricity_cost.html +31 -0
- esfex/visualization/resources/electricity_cost.js +305 -0
- esfex/visualization/resources/elk.bundled.js +6696 -0
- esfex/visualization/resources/flex_reliability.html +31 -0
- esfex/visualization/resources/flex_reliability.js +305 -0
- esfex/visualization/resources/font_scale.js +139 -0
- esfex/visualization/resources/fuel_supply.html +31 -0
- esfex/visualization/resources/fuel_supply.js +190 -0
- esfex/visualization/resources/images/layers-2x.png +0 -0
- esfex/visualization/resources/images/layers.png +0 -0
- esfex/visualization/resources/images/marker-icon-2x.png +0 -0
- esfex/visualization/resources/images/marker-icon.png +0 -0
- esfex/visualization/resources/images/marker-shadow.png +0 -0
- esfex/visualization/resources/images/spritesheet-2x.png +0 -0
- esfex/visualization/resources/images/spritesheet.png +0 -0
- esfex/visualization/resources/images/spritesheet.svg +156 -0
- esfex/visualization/resources/inter_node_flows.html +31 -0
- esfex/visualization/resources/inter_node_flows.js +197 -0
- esfex/visualization/resources/leaflet.css +661 -0
- esfex/visualization/resources/leaflet.draw.css +10 -0
- esfex/visualization/resources/leaflet.draw.js +10 -0
- esfex/visualization/resources/leaflet.html +96 -0
- esfex/visualization/resources/leaflet.js +6 -0
- esfex/visualization/resources/map_controller.js +3360 -0
- esfex/visualization/resources/mga_annotated_dendrogram.html +18 -0
- esfex/visualization/resources/mga_annotated_dendrogram.js +215 -0
- esfex/visualization/resources/mga_composition.html +18 -0
- esfex/visualization/resources/mga_composition.js +109 -0
- esfex/visualization/resources/mga_decision_factors.html +18 -0
- esfex/visualization/resources/mga_decision_factors.js +260 -0
- esfex/visualization/resources/mga_parcoords.html +25 -0
- esfex/visualization/resources/mga_parcoords.js +61 -0
- esfex/visualization/resources/mga_pathway.html +25 -0
- esfex/visualization/resources/mga_pathway.js +91 -0
- esfex/visualization/resources/mga_projection.html +18 -0
- esfex/visualization/resources/mga_projection.js +192 -0
- esfex/visualization/resources/mga_robustness_frontier.html +18 -0
- esfex/visualization/resources/mga_robustness_frontier.js +269 -0
- esfex/visualization/resources/mga_similarity.html +18 -0
- esfex/visualization/resources/mga_similarity.js +212 -0
- esfex/visualization/resources/mga_spatial.html +25 -0
- esfex/visualization/resources/mga_spatial.js +101 -0
- esfex/visualization/resources/mix_chart.html +31 -0
- esfex/visualization/resources/mix_chart.js +331 -0
- esfex/visualization/resources/net_load_heatmap.html +31 -0
- esfex/visualization/resources/net_load_heatmap.js +192 -0
- esfex/visualization/resources/plotly.min.js +8 -0
- esfex/visualization/resources/price_duration.html +31 -0
- esfex/visualization/resources/price_duration.js +169 -0
- esfex/visualization/resources/revenue_profitability.html +31 -0
- esfex/visualization/resources/revenue_profitability.js +216 -0
- esfex/visualization/resources/sankey.html +31 -0
- esfex/visualization/resources/sankey.js +68 -0
- esfex/visualization/resources/sld.html +87 -0
- esfex/visualization/resources/sld_controller.js +3042 -0
- esfex/visualization/resources/system_metrics.html +31 -0
- esfex/visualization/resources/system_metrics.js +213 -0
- esfex/visualization/resources/tech_performance.html +31 -0
- esfex/visualization/resources/tech_performance.js +270 -0
- esfex/visualization/resources/terminal.html +184 -0
- esfex/visualization/resources/uc_commitment_heatmap.html +31 -0
- esfex/visualization/resources/uc_commitment_heatmap.js +111 -0
- esfex/visualization/resources/uc_dispatch_stack.html +31 -0
- esfex/visualization/resources/uc_dispatch_stack.js +188 -0
- esfex/visualization/resources/uc_hourly_price.html +31 -0
- esfex/visualization/resources/uc_hourly_price.js +124 -0
- esfex/visualization/resources/uc_lmp_by_node.html +21 -0
- esfex/visualization/resources/uc_lmp_by_node.js +114 -0
- esfex/visualization/resources/uc_loadshed_curtailment.html +31 -0
- esfex/visualization/resources/uc_loadshed_curtailment.js +169 -0
- esfex/visualization/resources/uc_marginal_tech.html +31 -0
- esfex/visualization/resources/uc_marginal_tech.js +160 -0
- esfex/visualization/resources/uc_netload_duration.html +21 -0
- esfex/visualization/resources/uc_netload_duration.js +109 -0
- esfex/visualization/resources/uc_price_duration.html +31 -0
- esfex/visualization/resources/uc_price_duration.js +114 -0
- esfex/visualization/resources/uc_ramp_distribution.html +21 -0
- esfex/visualization/resources/uc_ramp_distribution.js +102 -0
- esfex/visualization/resources/uc_storage_soc.html +31 -0
- esfex/visualization/resources/uc_storage_soc.js +113 -0
- esfex/visualization/resources/world_countries.geojson +1 -0
- esfex/visualization/resources/xterm-addon-fit.js +2 -0
- esfex/visualization/resources/xterm.css +285 -0
- esfex/visualization/resources/xterm.js +2 -0
- esfex/visualization/run_output_view.py +242 -0
- esfex/visualization/scripting_api.py +1062 -0
- esfex/visualization/sld/__init__.py +1 -0
- esfex/visualization/sld/graph_builder.py +700 -0
- esfex/visualization/sld/sld_results_loader.py +490 -0
- esfex/visualization/sld/voltage_colors.py +61 -0
- esfex/visualization/sld_widget.py +107 -0
- esfex/visualization/splash.py +160 -0
- esfex/visualization/theme.py +1855 -0
- esfex/visualization/translations/__init__.py +0 -0
- esfex/visualization/translations/en.json +2389 -0
- esfex/visualization/translations/es.json +2350 -0
- esfex/visualization/translations/ja.json +2396 -0
- esfex/visualization/workflows/__init__.py +1 -0
- esfex/visualization/workflows/_qt_adapters.py +101 -0
- esfex/visualization/workflows/_wizard_utils.py +83 -0
- esfex/visualization/workflows/data_fetchers.py +501 -0
- esfex/visualization/workflows/demand_analysis.py +417 -0
- esfex/visualization/workflows/demand_distribution_steps.py +986 -0
- esfex/visualization/workflows/demand_distribution_wizard.py +260 -0
- esfex/visualization/workflows/demand_estimation_analysis.py +1776 -0
- esfex/visualization/workflows/demand_estimation_fetchers.py +1090 -0
- esfex/visualization/workflows/demand_estimation_steps.py +3010 -0
- esfex/visualization/workflows/demand_estimation_wizard.py +325 -0
- esfex/visualization/workflows/ev_advanced_steps.py +1159 -0
- esfex/visualization/workflows/ev_fetchers.py +262 -0
- esfex/visualization/workflows/ev_steps.py +1075 -0
- esfex/visualization/workflows/ev_wizard.py +333 -0
- esfex/visualization/workflows/financial_charts.py +653 -0
- esfex/visualization/workflows/financial_steps.py +1035 -0
- esfex/visualization/workflows/financial_wizard.py +344 -0
- esfex/visualization/workflows/grid_mapping_builder.py +1037 -0
- esfex/visualization/workflows/grid_mapping_clustering.py +544 -0
- esfex/visualization/workflows/grid_mapping_fetchers.py +1839 -0
- esfex/visualization/workflows/grid_mapping_inference.py +279 -0
- esfex/visualization/workflows/grid_mapping_quality.py +1069 -0
- esfex/visualization/workflows/grid_mapping_steps.py +4986 -0
- esfex/visualization/workflows/grid_mapping_wizard.py +300 -0
- esfex/visualization/workflows/otec_studio/__init__.py +23 -0
- esfex/visualization/workflows/otec_studio/cycle_panel.py +285 -0
- esfex/visualization/workflows/otec_studio/cycles.py +128 -0
- esfex/visualization/workflows/otec_studio/economics.py +138 -0
- esfex/visualization/workflows/otec_studio/economics_panel.py +262 -0
- esfex/visualization/workflows/otec_studio/engineering.py +186 -0
- esfex/visualization/workflows/otec_studio/operation.py +100 -0
- esfex/visualization/workflows/otec_studio/operation_panel.py +372 -0
- esfex/visualization/workflows/otec_studio/optimization_panel.py +396 -0
- esfex/visualization/workflows/otec_studio/optimize.py +183 -0
- esfex/visualization/workflows/otec_studio/project.py +282 -0
- esfex/visualization/workflows/otec_studio/regional.py +122 -0
- esfex/visualization/workflows/otec_studio/regional_panel.py +295 -0
- esfex/visualization/workflows/otec_studio/resource.py +139 -0
- esfex/visualization/workflows/otec_studio/resource_panel.py +329 -0
- esfex/visualization/workflows/otec_studio/uq.py +119 -0
- esfex/visualization/workflows/otec_studio/uq_panel.py +343 -0
- esfex/visualization/workflows/otec_studio/window.py +337 -0
- esfex/visualization/workflows/otec_studio/workers.py +210 -0
- esfex/visualization/workflows/otec_studio/zones.py +170 -0
- esfex/visualization/workflows/risk_charts.py +949 -0
- esfex/visualization/workflows/risk_panels.py +2971 -0
- esfex/visualization/workflows/risk_wizard.py +542 -0
- esfex/visualization/workflows/solar_adoption_steps.py +1302 -0
- esfex/visualization/workflows/solar_analysis.py +337 -0
- esfex/visualization/workflows/solar_macro_fetchers.py +731 -0
- esfex/visualization/workflows/solar_pv_advanced_steps.py +1435 -0
- esfex/visualization/workflows/solar_pv_analysis.py +1385 -0
- esfex/visualization/workflows/solar_pv_steps.py +1382 -0
- esfex/visualization/workflows/solar_pv_wizard.py +409 -0
- esfex/visualization/workflows/solar_rooftop_steps.py +798 -0
- esfex/visualization/workflows/solar_rooftop_wizard.py +397 -0
- esfex/visualization/workflows/wind_advanced_steps.py +1425 -0
- esfex/visualization/workflows/wind_analysis.py +1555 -0
- esfex/visualization/workflows/wind_steps.py +1320 -0
- esfex/visualization/workflows/wind_wizard.py +396 -0
- esfex/zones.py +532 -0
- esfex-0.1.0.dist-info/METADATA +421 -0
- esfex-0.1.0.dist-info/RECORD +369 -0
- esfex-0.1.0.dist-info/WHEEL +5 -0
- esfex-0.1.0.dist-info/entry_points.txt +2 -0
- esfex-0.1.0.dist-info/licenses/LICENSE +201 -0
- esfex-0.1.0.dist-info/top_level.txt +1 -0
esfex/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ESFEX: Energy System FlEXibility — Power System Optimization
|
|
3
|
+
|
|
4
|
+
A hybrid Python/Julia optimization framework for power system planning and operation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
8
|
+
__author__ = "Manuel Soto Calvo & Han Soo Lee"
|
|
9
|
+
|
|
10
|
+
from esfex.config.loader import load_config
|
|
11
|
+
from esfex.config.schema import ESFEXConfig
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"__version__",
|
|
15
|
+
"load_config",
|
|
16
|
+
"ESFEXConfig",
|
|
17
|
+
]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Power system dynamic analysis modules.
|
|
2
|
+
|
|
3
|
+
This package provides post-optimization analysis tools for frequency
|
|
4
|
+
stability, N-1 contingency assessment, and related calculations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from esfex.analysis.ac_types import ACPowerFlowResult, ShortCircuitResult
|
|
8
|
+
from esfex.analysis.frequency import (
|
|
9
|
+
FrequencyAnalyzer,
|
|
10
|
+
FrequencyResponse,
|
|
11
|
+
build_gen_freq_params_from_state,
|
|
12
|
+
)
|
|
13
|
+
from esfex.analysis.contingency import ContingencyAnalyzer, ContingencyResult
|
|
14
|
+
from esfex.analysis.ac_contingency import ACContingencyAnalyzer, ACContingencyResult
|
|
15
|
+
from esfex.analysis.n1_assessment import IntegratedN1Analyzer, N1SecurityAssessment
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"ACPowerFlowResult",
|
|
19
|
+
"ShortCircuitResult",
|
|
20
|
+
"FrequencyAnalyzer",
|
|
21
|
+
"FrequencyResponse",
|
|
22
|
+
"build_gen_freq_params_from_state",
|
|
23
|
+
"ContingencyAnalyzer",
|
|
24
|
+
"ContingencyResult",
|
|
25
|
+
"ACContingencyAnalyzer",
|
|
26
|
+
"ACContingencyResult",
|
|
27
|
+
"IntegratedN1Analyzer",
|
|
28
|
+
"N1SecurityAssessment",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
# Conditional pandapower exports
|
|
32
|
+
try:
|
|
33
|
+
from esfex.analysis.pandapower_bridge import PandapowerBridge
|
|
34
|
+
|
|
35
|
+
__all__ += ["PandapowerBridge"]
|
|
36
|
+
except ImportError:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
# Native AC bridge (always available — no external dependencies)
|
|
40
|
+
try:
|
|
41
|
+
from esfex.analysis.native_ac_bridge import NativeACBridge
|
|
42
|
+
|
|
43
|
+
__all__ += ["NativeACBridge"]
|
|
44
|
+
except ImportError:
|
|
45
|
+
pass
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""AC N-1 contingency analysis using pandapower.
|
|
2
|
+
|
|
3
|
+
Wraps ``PandapowerBridge`` with the same API as ``ContingencyAnalyzer``
|
|
4
|
+
(DC), but produces AC power flow results including voltage violations
|
|
5
|
+
and reactive power. Falls back to the DC analyzer if pandapower
|
|
6
|
+
diverges for a given contingency.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from esfex.analysis.contingency import ContingencyAnalyzer, ContingencyResult
|
|
16
|
+
|
|
17
|
+
log = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ACContingencyResult(ContingencyResult):
|
|
22
|
+
"""Extended contingency result with AC-specific fields.
|
|
23
|
+
|
|
24
|
+
Inherits all fields from ``ContingencyResult`` and adds voltage
|
|
25
|
+
data from the AC power flow solution.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
post_vm_pu: dict[str, float] = field(default_factory=dict)
|
|
29
|
+
voltage_violations: list[dict[str, Any]] = field(default_factory=list)
|
|
30
|
+
ac_converged: bool = True
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ACContingencyAnalyzer:
|
|
34
|
+
"""N-1 contingency analyzer using AC power flow via pandapower.
|
|
35
|
+
|
|
36
|
+
Uses the same public API as ``ContingencyAnalyzer`` so it can be
|
|
37
|
+
a drop-in replacement when pandapower is available.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
bridge : PandapowerBridge
|
|
42
|
+
Configured bridge (state already set).
|
|
43
|
+
dc_fallback : ContingencyAnalyzer | None
|
|
44
|
+
DC analyzer used as fallback when AC PF diverges.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
bridge: Any,
|
|
50
|
+
dc_fallback: ContingencyAnalyzer | None = None,
|
|
51
|
+
) -> None:
|
|
52
|
+
self._bridge = bridge
|
|
53
|
+
self._dc_fallback = dc_fallback
|
|
54
|
+
|
|
55
|
+
# ── Main API (matches ContingencyAnalyzer) ──
|
|
56
|
+
|
|
57
|
+
def analyze_generator_loss(
|
|
58
|
+
self,
|
|
59
|
+
snapshot: dict[str, Any],
|
|
60
|
+
gen_element_id: str,
|
|
61
|
+
) -> ACContingencyResult:
|
|
62
|
+
"""Compute post-contingency state after losing a generator.
|
|
63
|
+
|
|
64
|
+
Sets the generator out of service, redistributes its output
|
|
65
|
+
to other dispatchable generators, and reruns AC power flow.
|
|
66
|
+
|
|
67
|
+
Falls back to DC analysis if AC PF diverges.
|
|
68
|
+
"""
|
|
69
|
+
from esfex.analysis.ac_types import ACPowerFlowResult # noqa: F401
|
|
70
|
+
|
|
71
|
+
gens_data = snapshot.get("generators", {})
|
|
72
|
+
lost_output = gens_data.get(gen_element_id, {}).get("output_mw", 0.0)
|
|
73
|
+
|
|
74
|
+
if lost_output <= 0:
|
|
75
|
+
return ACContingencyResult(
|
|
76
|
+
contingency_type="generator",
|
|
77
|
+
element_id=gen_element_id,
|
|
78
|
+
element_description=f"Generator {gen_element_id} (offline)",
|
|
79
|
+
is_secure=True,
|
|
80
|
+
ac_converged=True,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Collect pre-contingency state
|
|
84
|
+
pre_gen = {eid: gd.get("output_mw", 0.0) for eid, gd in gens_data.items()}
|
|
85
|
+
pre_flow = {
|
|
86
|
+
eid: ld.get("flow_mw", 0.0)
|
|
87
|
+
for eid, ld in snapshot.get("lines", {}).items()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Trip the generator in the existing network
|
|
91
|
+
net = self._bridge.get_network()
|
|
92
|
+
if net is None:
|
|
93
|
+
return self._dc_fallback_gen(snapshot, gen_element_id)
|
|
94
|
+
|
|
95
|
+
# Set generator out of service
|
|
96
|
+
gen_type = self._find_gen_type(gen_element_id)
|
|
97
|
+
if gen_type:
|
|
98
|
+
self._bridge.set_element_in_service(gen_type, gen_element_id, False)
|
|
99
|
+
|
|
100
|
+
# Redistribute lost generation to remaining dispatchable gens
|
|
101
|
+
self._redistribute_generation(net, gen_element_id, lost_output, gens_data)
|
|
102
|
+
|
|
103
|
+
# Rerun AC power flow
|
|
104
|
+
pf_result = self._bridge.rerun_power_flow()
|
|
105
|
+
|
|
106
|
+
# Restore generator
|
|
107
|
+
if gen_type:
|
|
108
|
+
self._bridge.set_element_in_service(gen_type, gen_element_id, True)
|
|
109
|
+
|
|
110
|
+
if not pf_result.converged:
|
|
111
|
+
log.info("AC PF diverged for gen loss %s, falling back to DC", gen_element_id)
|
|
112
|
+
return self._dc_fallback_gen(snapshot, gen_element_id)
|
|
113
|
+
|
|
114
|
+
# Build post-contingency generation
|
|
115
|
+
post_gen = dict(pre_gen)
|
|
116
|
+
post_gen[gen_element_id] = 0.0
|
|
117
|
+
for gid, p_mw in pf_result.gen_p_mw.items():
|
|
118
|
+
if gid != gen_element_id:
|
|
119
|
+
post_gen[gid] = p_mw
|
|
120
|
+
|
|
121
|
+
# Build post-contingency flows
|
|
122
|
+
post_flow: dict[str, float] = {}
|
|
123
|
+
overloaded = []
|
|
124
|
+
max_overload = 0.0
|
|
125
|
+
for edge_id, p_from in pf_result.line_p_from_mw.items():
|
|
126
|
+
post_flow[edge_id] = p_from
|
|
127
|
+
loading = pf_result.line_loading_pct.get(edge_id, 0.0)
|
|
128
|
+
if loading > 100.0:
|
|
129
|
+
overload_pct = loading - 100.0
|
|
130
|
+
max_overload = max(max_overload, overload_pct)
|
|
131
|
+
line_id = edge_id.replace("edge_", "")
|
|
132
|
+
overloaded.append({
|
|
133
|
+
"line_id": line_id,
|
|
134
|
+
"edge_id": edge_id,
|
|
135
|
+
"flow_mw": round(p_from, 2),
|
|
136
|
+
"capacity_mw": 0.0, # TODO: resolve from snapshot
|
|
137
|
+
"overload_pct": round(overload_pct, 1),
|
|
138
|
+
"loading_pct": round(loading, 1),
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
# Resolve line capacities for overloaded lines
|
|
142
|
+
lines_data = snapshot.get("lines", {})
|
|
143
|
+
for ol in overloaded:
|
|
144
|
+
cap = lines_data.get(ol["edge_id"], {}).get("capacity_mw", 0.0)
|
|
145
|
+
ol["capacity_mw"] = round(cap, 2)
|
|
146
|
+
|
|
147
|
+
return ACContingencyResult(
|
|
148
|
+
contingency_type="generator",
|
|
149
|
+
element_id=gen_element_id,
|
|
150
|
+
element_description=f"Loss of {gen_element_id} ({lost_output:.1f} MW)",
|
|
151
|
+
pre_gen_mw=pre_gen,
|
|
152
|
+
pre_flow_mw=pre_flow,
|
|
153
|
+
post_gen_mw={k: round(v, 2) for k, v in post_gen.items()},
|
|
154
|
+
post_flow_mw=post_flow,
|
|
155
|
+
overloaded_lines=overloaded,
|
|
156
|
+
is_secure=len(overloaded) == 0 and len(pf_result.voltage_violations) == 0,
|
|
157
|
+
max_overload_pct=round(max_overload, 1),
|
|
158
|
+
post_vm_pu=pf_result.bus_vm_pu,
|
|
159
|
+
voltage_violations=pf_result.voltage_violations,
|
|
160
|
+
ac_converged=True,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def analyze_line_loss(
|
|
164
|
+
self,
|
|
165
|
+
snapshot: dict[str, Any],
|
|
166
|
+
line_id: str,
|
|
167
|
+
) -> ACContingencyResult:
|
|
168
|
+
"""Compute post-contingency state after losing a transmission line."""
|
|
169
|
+
pre_gen = {
|
|
170
|
+
eid: gd.get("output_mw", 0.0)
|
|
171
|
+
for eid, gd in snapshot.get("generators", {}).items()
|
|
172
|
+
}
|
|
173
|
+
pre_flow = {
|
|
174
|
+
eid: ld.get("flow_mw", 0.0)
|
|
175
|
+
for eid, ld in snapshot.get("lines", {}).items()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
net = self._bridge.get_network()
|
|
179
|
+
if net is None:
|
|
180
|
+
return self._dc_fallback_line(snapshot, line_id)
|
|
181
|
+
|
|
182
|
+
# Take line out of service
|
|
183
|
+
self._bridge.set_element_in_service("line", line_id, False)
|
|
184
|
+
|
|
185
|
+
# Rerun AC power flow
|
|
186
|
+
pf_result = self._bridge.rerun_power_flow()
|
|
187
|
+
|
|
188
|
+
# Restore line
|
|
189
|
+
self._bridge.set_element_in_service("line", line_id, True)
|
|
190
|
+
|
|
191
|
+
if not pf_result.converged:
|
|
192
|
+
log.info("AC PF diverged for line loss %s, falling back to DC", line_id)
|
|
193
|
+
return self._dc_fallback_line(snapshot, line_id)
|
|
194
|
+
|
|
195
|
+
# Post-contingency flows
|
|
196
|
+
post_flow: dict[str, float] = {}
|
|
197
|
+
overloaded = []
|
|
198
|
+
max_overload = 0.0
|
|
199
|
+
tripped_edge = f"edge_{line_id}"
|
|
200
|
+
lines_data = snapshot.get("lines", {})
|
|
201
|
+
|
|
202
|
+
for edge_id, p_from in pf_result.line_p_from_mw.items():
|
|
203
|
+
post_flow[edge_id] = p_from
|
|
204
|
+
loading = pf_result.line_loading_pct.get(edge_id, 0.0)
|
|
205
|
+
if loading > 100.0:
|
|
206
|
+
overload_pct = loading - 100.0
|
|
207
|
+
max_overload = max(max_overload, overload_pct)
|
|
208
|
+
lid = edge_id.replace("edge_", "")
|
|
209
|
+
cap = lines_data.get(edge_id, {}).get("capacity_mw", 0.0)
|
|
210
|
+
overloaded.append({
|
|
211
|
+
"line_id": lid,
|
|
212
|
+
"edge_id": edge_id,
|
|
213
|
+
"flow_mw": round(p_from, 2),
|
|
214
|
+
"capacity_mw": round(cap, 2),
|
|
215
|
+
"overload_pct": round(overload_pct, 1),
|
|
216
|
+
"loading_pct": round(loading, 1),
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
post_flow[tripped_edge] = 0.0
|
|
220
|
+
|
|
221
|
+
cap_mw = lines_data.get(tripped_edge, {}).get("capacity_mw", 0.0)
|
|
222
|
+
|
|
223
|
+
return ACContingencyResult(
|
|
224
|
+
contingency_type="line",
|
|
225
|
+
element_id=line_id,
|
|
226
|
+
element_description=f"Loss of line {line_id} ({cap_mw:.0f} MW cap)",
|
|
227
|
+
pre_gen_mw=pre_gen,
|
|
228
|
+
pre_flow_mw=pre_flow,
|
|
229
|
+
post_gen_mw={k: round(v, 2) for k, v in pre_gen.items()},
|
|
230
|
+
post_flow_mw=post_flow,
|
|
231
|
+
overloaded_lines=overloaded,
|
|
232
|
+
is_secure=len(overloaded) == 0 and len(pf_result.voltage_violations) == 0,
|
|
233
|
+
max_overload_pct=round(max_overload, 1),
|
|
234
|
+
post_vm_pu=pf_result.bus_vm_pu,
|
|
235
|
+
voltage_violations=pf_result.voltage_violations,
|
|
236
|
+
ac_converged=True,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def get_contingency_list(
|
|
240
|
+
self, snapshot: dict[str, Any],
|
|
241
|
+
) -> list[dict[str, Any]]:
|
|
242
|
+
"""Return all possible contingencies (same format as DC version)."""
|
|
243
|
+
contingencies: list[dict[str, Any]] = []
|
|
244
|
+
|
|
245
|
+
gens_data = snapshot.get("generators", {})
|
|
246
|
+
for eid, gdata in gens_data.items():
|
|
247
|
+
output = gdata.get("output_mw", 0.0)
|
|
248
|
+
status = gdata.get("status", 1)
|
|
249
|
+
if status > 0 and output > 0.1:
|
|
250
|
+
contingencies.append({
|
|
251
|
+
"type": "generator",
|
|
252
|
+
"element_id": eid,
|
|
253
|
+
"description": f"Loss: {eid} ({output:.0f} MW)",
|
|
254
|
+
"impact_mw": output,
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
for edge_id, ldata in snapshot.get("lines", {}).items():
|
|
258
|
+
line_id = edge_id.replace("edge_", "")
|
|
259
|
+
flow = abs(ldata.get("flow_mw", 0.0))
|
|
260
|
+
cap = ldata.get("capacity_mw", 0.0)
|
|
261
|
+
contingencies.append({
|
|
262
|
+
"type": "line",
|
|
263
|
+
"element_id": line_id,
|
|
264
|
+
"description": f"Loss: {line_id} ({cap:.0f} MW cap)",
|
|
265
|
+
"impact_mw": flow,
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
contingencies.sort(key=lambda c: c["impact_mw"], reverse=True)
|
|
269
|
+
return contingencies
|
|
270
|
+
|
|
271
|
+
# ── Private helpers ──
|
|
272
|
+
|
|
273
|
+
def _find_gen_type(self, gen_id: str) -> str | None:
|
|
274
|
+
"""Determine the pandapower element type for a generator ID."""
|
|
275
|
+
if gen_id in self._bridge._gen_id_to_pp:
|
|
276
|
+
return "gen"
|
|
277
|
+
if gen_id in self._bridge._sgen_id_to_pp:
|
|
278
|
+
return "sgen"
|
|
279
|
+
return None
|
|
280
|
+
|
|
281
|
+
def _redistribute_generation(
|
|
282
|
+
self, net: Any, tripped_id: str, lost_mw: float,
|
|
283
|
+
gens_data: dict[str, Any],
|
|
284
|
+
) -> None:
|
|
285
|
+
"""Redistribute lost generation to remaining dispatchable gens."""
|
|
286
|
+
import pandapower as pp # noqa: F401
|
|
287
|
+
|
|
288
|
+
# Find dispatchable gens with headroom
|
|
289
|
+
headroom: dict[str, float] = {}
|
|
290
|
+
for gen_id, pp_idx in self._bridge._gen_id_to_pp.items():
|
|
291
|
+
if gen_id == tripped_id:
|
|
292
|
+
continue
|
|
293
|
+
if not net.gen.at[pp_idx, "in_service"]:
|
|
294
|
+
continue
|
|
295
|
+
current = gens_data.get(gen_id, {}).get("output_mw", 0.0)
|
|
296
|
+
rated = gens_data.get(gen_id, {}).get("capacity_mw", 0.0)
|
|
297
|
+
room = max(0.0, rated - current)
|
|
298
|
+
if room > 0:
|
|
299
|
+
headroom[gen_id] = room
|
|
300
|
+
|
|
301
|
+
total_headroom = sum(headroom.values())
|
|
302
|
+
if total_headroom <= 0:
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
# Pro-rata increase
|
|
306
|
+
for gen_id, room in headroom.items():
|
|
307
|
+
increase = min(lost_mw, total_headroom) * (room / total_headroom)
|
|
308
|
+
pp_idx = self._bridge._gen_id_to_pp[gen_id]
|
|
309
|
+
current_p = float(net.gen.at[pp_idx, "p_mw"])
|
|
310
|
+
net.gen.at[pp_idx, "p_mw"] = current_p + increase
|
|
311
|
+
|
|
312
|
+
def _dc_fallback_gen(
|
|
313
|
+
self, snapshot: dict, gen_id: str,
|
|
314
|
+
) -> ACContingencyResult:
|
|
315
|
+
"""Fall back to DC contingency for generator loss."""
|
|
316
|
+
if self._dc_fallback is not None:
|
|
317
|
+
dc_result = self._dc_fallback.analyze_generator_loss(snapshot, gen_id)
|
|
318
|
+
return ACContingencyResult(
|
|
319
|
+
contingency_type=dc_result.contingency_type,
|
|
320
|
+
element_id=dc_result.element_id,
|
|
321
|
+
element_description=dc_result.element_description + " [DC fallback]",
|
|
322
|
+
pre_gen_mw=dc_result.pre_gen_mw,
|
|
323
|
+
pre_flow_mw=dc_result.pre_flow_mw,
|
|
324
|
+
post_gen_mw=dc_result.post_gen_mw,
|
|
325
|
+
post_flow_mw=dc_result.post_flow_mw,
|
|
326
|
+
load_shed_mw=dc_result.load_shed_mw,
|
|
327
|
+
overloaded_lines=dc_result.overloaded_lines,
|
|
328
|
+
total_load_shed_mw=dc_result.total_load_shed_mw,
|
|
329
|
+
is_secure=dc_result.is_secure,
|
|
330
|
+
max_overload_pct=dc_result.max_overload_pct,
|
|
331
|
+
ac_converged=False,
|
|
332
|
+
)
|
|
333
|
+
return ACContingencyResult(
|
|
334
|
+
contingency_type="generator",
|
|
335
|
+
element_id=gen_id,
|
|
336
|
+
element_description=f"Loss of {gen_id} [AC diverged, no DC fallback]",
|
|
337
|
+
ac_converged=False,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
def _dc_fallback_line(
|
|
341
|
+
self, snapshot: dict, line_id: str,
|
|
342
|
+
) -> ACContingencyResult:
|
|
343
|
+
"""Fall back to DC contingency for line loss."""
|
|
344
|
+
if self._dc_fallback is not None:
|
|
345
|
+
dc_result = self._dc_fallback.analyze_line_loss(snapshot, line_id)
|
|
346
|
+
return ACContingencyResult(
|
|
347
|
+
contingency_type=dc_result.contingency_type,
|
|
348
|
+
element_id=dc_result.element_id,
|
|
349
|
+
element_description=dc_result.element_description + " [DC fallback]",
|
|
350
|
+
pre_gen_mw=dc_result.pre_gen_mw,
|
|
351
|
+
pre_flow_mw=dc_result.pre_flow_mw,
|
|
352
|
+
post_gen_mw=dc_result.post_gen_mw,
|
|
353
|
+
post_flow_mw=dc_result.post_flow_mw,
|
|
354
|
+
load_shed_mw=dc_result.load_shed_mw,
|
|
355
|
+
overloaded_lines=dc_result.overloaded_lines,
|
|
356
|
+
total_load_shed_mw=dc_result.total_load_shed_mw,
|
|
357
|
+
is_secure=dc_result.is_secure,
|
|
358
|
+
max_overload_pct=dc_result.max_overload_pct,
|
|
359
|
+
ac_converged=False,
|
|
360
|
+
)
|
|
361
|
+
return ACContingencyResult(
|
|
362
|
+
contingency_type="line",
|
|
363
|
+
element_id=line_id,
|
|
364
|
+
element_description=f"Loss of line {line_id} [AC diverged, no DC fallback]",
|
|
365
|
+
ac_converged=False,
|
|
366
|
+
)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Shared result types for AC power flow and short-circuit analysis.
|
|
2
|
+
|
|
3
|
+
These dataclasses are backend-agnostic — used by both the native Julia
|
|
4
|
+
Newton-Raphson solver (``NativeACBridge``) and the pandapower bridge
|
|
5
|
+
(``PandapowerBridge``).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ACPowerFlowResult:
|
|
16
|
+
"""Results from an AC Newton-Raphson power flow."""
|
|
17
|
+
|
|
18
|
+
converged: bool = False
|
|
19
|
+
iterations: int = 0
|
|
20
|
+
|
|
21
|
+
# Per bus: bus_id → values
|
|
22
|
+
bus_vm_pu: dict[str, float] = field(default_factory=dict)
|
|
23
|
+
bus_va_deg: dict[str, float] = field(default_factory=dict)
|
|
24
|
+
bus_p_mw: dict[str, float] = field(default_factory=dict)
|
|
25
|
+
bus_q_mvar: dict[str, float] = field(default_factory=dict)
|
|
26
|
+
|
|
27
|
+
# Per line: edge_id → values
|
|
28
|
+
line_p_from_mw: dict[str, float] = field(default_factory=dict)
|
|
29
|
+
line_q_from_mvar: dict[str, float] = field(default_factory=dict)
|
|
30
|
+
line_p_loss_mw: dict[str, float] = field(default_factory=dict)
|
|
31
|
+
line_loading_pct: dict[str, float] = field(default_factory=dict)
|
|
32
|
+
|
|
33
|
+
# Per generator: gen_id → values
|
|
34
|
+
gen_p_mw: dict[str, float] = field(default_factory=dict)
|
|
35
|
+
gen_q_mvar: dict[str, float] = field(default_factory=dict)
|
|
36
|
+
|
|
37
|
+
# Summary
|
|
38
|
+
total_losses_mw: float = 0.0
|
|
39
|
+
voltage_violations: list[dict[str, Any]] = field(default_factory=list)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class ShortCircuitResult:
|
|
44
|
+
"""Results from IEC 60909 short-circuit analysis."""
|
|
45
|
+
|
|
46
|
+
# Per bus: bus_id → values
|
|
47
|
+
ik_ka: dict[str, float] = field(default_factory=dict) # Initial SC current
|
|
48
|
+
ip_ka: dict[str, float] = field(default_factory=dict) # Peak SC current
|
|
49
|
+
sk_mva: dict[str, float] = field(default_factory=dict) # SC power
|