gridstate 2.7.0__tar.gz → 2.8.0__tar.gz
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.
- {gridstate-2.7.0 → gridstate-2.8.0}/CHANGELOG.md +32 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/PKG-INFO +1 -1
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/_version.py +3 -3
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/algorithms/ipm_pd.py +86 -2
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/api.py +73 -2
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/pipeline/config.py +10 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/pipeline/passes.py +1 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/state.py +52 -1
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate.egg-info/PKG-INFO +1 -1
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate.egg-info/scm_version.json +2 -2
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/golden/case118.json +29 -29
- gridstate-2.8.0/tests/test_data/golden/case118.npz +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/golden/case118_degraded.json +40 -40
- gridstate-2.8.0/tests/test_data/golden/case118_degraded.npz +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/golden/case14.json +29 -29
- gridstate-2.8.0/tests/test_data/golden/case14.npz +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/golden/case14_degraded.json +36 -36
- gridstate-2.8.0/tests/test_data/golden/case14_degraded.npz +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/golden/manifest.json +52 -3
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_ipm_balance_modes.py +21 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_ipm_pd.py +57 -1
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_pipeline_warm_start.py +2 -2
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_state.py +54 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_state_range.py +5 -5
- gridstate-2.7.0/tests/test_data/golden/case118.npz +0 -0
- gridstate-2.7.0/tests/test_data/golden/case118_degraded.npz +0 -0
- gridstate-2.7.0/tests/test_data/golden/case14.npz +0 -0
- gridstate-2.7.0/tests/test_data/golden/case14_degraded.npz +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/.github/workflows/ci.yml +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/.github/workflows/release.yml +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/.gitignore +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/.pre-commit-config.yaml +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/LICENSE +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/Makefile +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/README.md +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/build_cython.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/__init__.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/__pyinstaller/__init__.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/__pyinstaller/hook-gridstate.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/_arrays.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/adapters/__init__.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/adapters/from_pandapower.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/algebra/__init__.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/algebra/_selinv_kernels.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/algebra/base.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/algebra/estimators.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/algebra/jacobian_pattern.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/algebra/normalized_residuals.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/algorithms/__init__.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/algorithms/_gram_numba.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/algorithms/ipm.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/algorithms/kkt_solver.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/algorithms/status.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/algorithms/wls.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/bad_data_repass.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/bounds.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/constants.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/contract/__init__.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/contract/_legacy.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/contract/containers.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/contract/derived.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/contract/derived_tables.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/contract/serialize.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/contract/tables.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/contract/validate.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/contract/version.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/ipm_hints.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/load_characteristics.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/losses.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/measurement_ids.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/pipeline/__init__.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/pipeline/context.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/pipeline/manifest.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/pipeline/orchestration.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/pipeline/preparation.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/pipeline/runner.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/pipeline/steps.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/post_processing.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/preprocessing/__init__.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/preprocessing/_scan.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/preprocessing/ipm_setup.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/preprocessing/meas_rows.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/preprocessing/mirror_voltage.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/preprocessing/node_props.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/preprocessing/pseudo_measurements.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/py.typed +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/quality_summary.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/resolve_pass.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/result.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/runtime.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/shunt_sanity.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/solution_hints.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/__init__.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/_filters.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/apply_resolved.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/generators.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/kinds.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/load_only_injection.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/loss_filter.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/measurements.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/noise.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/on_line.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/quality.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/rpn.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/shunts.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/sigma_floor.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/units.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/voltage_filter.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/telemetry/voltage_nominal.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/topology.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/units.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/utils.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/v_mirror.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/v_refine.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/validation/__init__.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/validation/_diagnostics.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/validation/bad_data.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/validation/chi2_test.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/validation/observability.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/working.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/ybus.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate/z_vector.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate.egg-info/SOURCES.txt +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate.egg-info/dependency_links.txt +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate.egg-info/entry_points.txt +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate.egg-info/requires.txt +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate.egg-info/scm_file_list.json +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/gridstate.egg-info/top_level.txt +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/pyproject.toml +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/setup.cfg +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/__init__.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/_golden.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/conftest.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_adapters_lazy_import.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_aggregate_generators.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_algebra_base.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_anti_overshoot.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_arrays_helpers.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_bad_data.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_bad_data_repass.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_bounds.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_boxed_balance.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_breaker_reactance.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_chain_mirror.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_contract.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_contract_edge_cases.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_contract_runtime.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/golden/case30.json +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/golden/case30.npz +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/golden/case30_degraded.json +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/golden/case30_degraded.npz +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/ieee/case118.npz +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/ieee/case118_truth.npz +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/ieee/case14.npz +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/ieee/case14_truth.npz +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/ieee/case30.npz +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_data/ieee/case30_truth.npz +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_derived_tables.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_enum_codes.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_flow_sigma_floor.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_from_pandapower.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_generator_shunt_kernels.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_golden.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_gram_numba.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_ieee_cases.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_ipm.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_ipm_hints.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_ipm_injection_roles.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_ipm_integration.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_jacobian_pattern.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_kkt_solver.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_load_characteristic_segments.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_load_only_injection.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_losses_breakdown.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_losses_q_convention.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_losses_trafo_classification.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_materialize_kernel.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_measurement_filter_kernels.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_measurement_ids.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_mirror_voltage_kernel.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_near_critical_ranking.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_neutral_wording.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_noise_model.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_normalized_residuals.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_numpy_only_runtime.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_observability.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_on_line_kernel.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_perf_synthetic_network.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_pipeline.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_pipeline_config_validation.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_pipeline_idempotent.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_pipeline_invariant_errors.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_pipeline_step_order.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_pipeline_working_layer.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_post_processing.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_preparation_steps.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_prepare_network.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_pseudo_unknown_load.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_pseudo_v_unobservable.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_quality_summary_paths.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_reconcile_balance.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_refine_merge.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_resolve_pass.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_result_quality_summary.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_rn_selected_inversion.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_serialize_npz.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_shunt_sanity.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_solution_hints.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_solve_status.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_telemetry_apply_kernel.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_telemetry_bounds.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_telemetry_physics_kernels.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_tm_code_quality.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_topology_kernels.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_topology_port.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_transformer_from_shunt.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_units.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_units_array_kernels.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_v_mirror.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_v_refine.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_variance_charging.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_version.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_voltage_calibration_gen_nodes.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_voltage_range_filter_fallback.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_wls.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_wls_robustness.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_working_container.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_ybus.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tests/test_z_vector.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tools/gen_ieee_fixtures.py +0 -0
- {gridstate-2.7.0 → gridstate-2.8.0}/tools/golden.py +0 -0
|
@@ -9,6 +9,38 @@ types of the merged pull requests; breaking and behavior-changing items are
|
|
|
9
9
|
listed separately under "Breaking changes" and "Behavior changes". A release is
|
|
10
10
|
cut by tagging the merged main commit; the version is derived from that tag.
|
|
11
11
|
|
|
12
|
+
## v2.8.0 (2026-10-18)
|
|
13
|
+
|
|
14
|
+
### Behavior changes
|
|
15
|
+
|
|
16
|
+
- `init="flat"` starts V consistent with the transformer ratios instead of
|
|
17
|
+
1 p.u. everywhere: `log V` from a least squares over the branches
|
|
18
|
+
(`log V_to − log V_from = −log k`, weight `1/|z|`, `log V_slack = 0`). A node
|
|
19
|
+
whose nominal voltage does not match its transformer ratio (or an off-nominal
|
|
20
|
+
tap on a low-impedance branch) no longer starts with a mismatch of hundreds
|
|
21
|
+
of p.u. Applies to every algorithm; without off-nominal ratios the start is
|
|
22
|
+
unchanged. WLS on an ill-conditioned model may converge to a neighbouring
|
|
23
|
+
solution (on the test models p95 |ΔV| against the reference within 0.05 kV).
|
|
24
|
+
- `ipm_pd` voltage guard on by default (`ipm_voltage_guard` /
|
|
25
|
+
`voltage_guard`, ignored by other algorithms): every node V is kept within a
|
|
26
|
+
box from its `voltage_min` / `voltage_max` (unset or implausible limits fall
|
|
27
|
+
back to 0.55 / 1.8 p.u., 0.7–1.5 p.u. with neither set) and one step changes
|
|
28
|
+
V by at most 20 %, an angle by at most 0.6 rad and a branch angle difference
|
|
29
|
+
by at most 0.35 rad. Where a node limit is active the estimate sits on it.
|
|
30
|
+
|
|
31
|
+
### Fix
|
|
32
|
+
|
|
33
|
+
- `ipm_pd` with the global hard balance no longer collapses a generator node
|
|
34
|
+
into the low-voltage power-flow branch from a flat start (V → 0, the node
|
|
35
|
+
angle undetermined, a formally converged V ≈ 0.2 p.u.).
|
|
36
|
+
|
|
37
|
+
### Feat
|
|
38
|
+
|
|
39
|
+
- `solve_ipm_pd`: `state_box_*` (barrier boxes on state coordinates that stay
|
|
40
|
+
under the trust region and the stopping test, without box damping) and the
|
|
41
|
+
`step_rel_*` / `step_abs_*` / `step_diff_*` step limits.
|
|
42
|
+
- `gridstate.state.tap_consistent_voltages`.
|
|
43
|
+
|
|
12
44
|
## v2.7.0 (2026-10-18)
|
|
13
45
|
|
|
14
46
|
### Behavior changes
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.
|
|
22
|
-
__version_tuple__ = version_tuple = (2,
|
|
21
|
+
__version__ = version = '2.8.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 8, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g89cfd10b4'
|
|
@@ -172,6 +172,37 @@ class _Linearization:
|
|
|
172
172
|
return dx, ds_l, ds_u, dy_l, dy_u, dlam
|
|
173
173
|
|
|
174
174
|
|
|
175
|
+
def _step_cap(
|
|
176
|
+
dx: np.ndarray,
|
|
177
|
+
x: np.ndarray,
|
|
178
|
+
rel_idx: np.ndarray,
|
|
179
|
+
rel_max: float,
|
|
180
|
+
abs_idx: np.ndarray,
|
|
181
|
+
abs_max: float,
|
|
182
|
+
pair_i: np.ndarray,
|
|
183
|
+
pair_j: np.ndarray,
|
|
184
|
+
diff_max: float,
|
|
185
|
+
) -> float:
|
|
186
|
+
"""Largest primal step length allowed by the per-coordinate step limits."""
|
|
187
|
+
cap = math.inf
|
|
188
|
+
if rel_max > 0.0 and rel_idx.size:
|
|
189
|
+
d = np.abs(dx[rel_idx])
|
|
190
|
+
lim = rel_max * np.abs(x[rel_idx])
|
|
191
|
+
k = d > 0.0
|
|
192
|
+
if k.any():
|
|
193
|
+
cap = min(cap, float(np.min(lim[k] / d[k])))
|
|
194
|
+
if abs_max > 0.0 and abs_idx.size:
|
|
195
|
+
d_max = float(np.max(np.abs(dx[abs_idx])))
|
|
196
|
+
if d_max > 0.0:
|
|
197
|
+
cap = min(cap, abs_max / d_max)
|
|
198
|
+
if diff_max > 0.0 and pair_i.size:
|
|
199
|
+
dz = np.append(dx, 0.0)
|
|
200
|
+
d_max = float(np.max(np.abs(dz[pair_i] - dz[pair_j])))
|
|
201
|
+
if d_max > 0.0:
|
|
202
|
+
cap = min(cap, diff_max / d_max)
|
|
203
|
+
return cap
|
|
204
|
+
|
|
205
|
+
|
|
175
206
|
@dataclass
|
|
176
207
|
class _Merit:
|
|
177
208
|
"""The merit ``φ`` of one iteration (see the module docstring); ``mu`` is the target ``σμ``."""
|
|
@@ -249,6 +280,15 @@ def solve_ipm_pd(
|
|
|
249
280
|
eq_prox: float = 1e-9,
|
|
250
281
|
box_prox: float = 1e-6,
|
|
251
282
|
box_prox_max: float = 1e-2,
|
|
283
|
+
state_box_idx: np.ndarray | None = None,
|
|
284
|
+
state_box_lo: np.ndarray | None = None,
|
|
285
|
+
state_box_hi: np.ndarray | None = None,
|
|
286
|
+
step_rel_idx: np.ndarray | None = None,
|
|
287
|
+
step_rel_max: float = 0.0,
|
|
288
|
+
step_abs_idx: np.ndarray | None = None,
|
|
289
|
+
step_abs_max: float = 0.0,
|
|
290
|
+
step_diff_pairs: tuple[np.ndarray, np.ndarray] | None = None,
|
|
291
|
+
step_diff_max: float = 0.0,
|
|
252
292
|
) -> IPMResult:
|
|
253
293
|
"""Mehrotra predictor-corrector IPM (see the module docstring).
|
|
254
294
|
|
|
@@ -297,6 +337,17 @@ def solve_ipm_pd(
|
|
|
297
337
|
conflicts into the residuals and make this markedly worse. ``0``
|
|
298
338
|
reweights to the end.
|
|
299
339
|
kkt_solver: Newton-system solver (``None`` — scipy).
|
|
340
|
+
state_box_idx, state_box_lo, state_box_hi: barrier boxes on state
|
|
341
|
+
coordinates (e.g. V). Unlike ``box_idx`` they stay under the trust
|
|
342
|
+
region and the stopping test and get no box damping: the box only
|
|
343
|
+
keeps the iteration away from an inadmissible region (V → 0, where
|
|
344
|
+
the node angle is undetermined).
|
|
345
|
+
step_rel_idx, step_rel_max: ``|αp·Δx_i| ≤ step_rel_max·|x_i|`` on these
|
|
346
|
+
coordinates (``0`` — off).
|
|
347
|
+
step_abs_idx, step_abs_max: ``|αp·Δx_i| ≤ step_abs_max`` (``0`` — off).
|
|
348
|
+
step_diff_pairs, step_diff_max: ``|αp·(Δx_i − Δx_j)| ≤ step_diff_max`` for
|
|
349
|
+
each pair ``(i, j)``; an index ``-1`` stands for a fixed zero (the
|
|
350
|
+
slack angle). ``0`` — off.
|
|
300
351
|
eq_prox: with equality rows, a proximal term ``eq_prox·max(ρ)`` on the
|
|
301
352
|
diagonal of the box coordinates. A hard balance fixes only the net
|
|
302
353
|
injection of a node, so the common level of its generation and load
|
|
@@ -335,6 +386,28 @@ def solve_ipm_pd(
|
|
|
335
386
|
raise ValueError("box_lo/box_hi contain pairs with lo >= hi")
|
|
336
387
|
else:
|
|
337
388
|
b_lo = b_hi = np.zeros(0)
|
|
389
|
+
n_var_box = box_idx.size
|
|
390
|
+
if state_box_idx is not None and np.size(state_box_idx):
|
|
391
|
+
if state_box_lo is None or state_box_hi is None:
|
|
392
|
+
raise ValueError("state_box_lo and state_box_hi are required with state_box_idx")
|
|
393
|
+
sb_lo = np.asarray(state_box_lo, dtype=np.float64)
|
|
394
|
+
sb_hi = np.asarray(state_box_hi, dtype=np.float64)
|
|
395
|
+
if np.any(sb_lo >= sb_hi):
|
|
396
|
+
raise ValueError("state_box_lo/state_box_hi contain pairs with lo >= hi")
|
|
397
|
+
box_idx = np.concatenate([box_idx, np.asarray(state_box_idx, dtype=np.int64)])
|
|
398
|
+
b_lo = np.concatenate([b_lo, sb_lo])
|
|
399
|
+
b_hi = np.concatenate([b_hi, sb_hi])
|
|
400
|
+
var_box_idx = box_idx[:n_var_box]
|
|
401
|
+
rel_idx = np.asarray(step_rel_idx if step_rel_idx is not None else [], dtype=np.int64)
|
|
402
|
+
abs_idx = np.asarray(step_abs_idx if step_abs_idx is not None else [], dtype=np.int64)
|
|
403
|
+
if step_diff_pairs is not None:
|
|
404
|
+
# -1 -> the appended zero coordinate.
|
|
405
|
+
pair_i = np.asarray(step_diff_pairs[0], dtype=np.int64)
|
|
406
|
+
pair_j = np.asarray(step_diff_pairs[1], dtype=np.int64)
|
|
407
|
+
pair_i = np.where(pair_i < 0, n, pair_i)
|
|
408
|
+
pair_j = np.where(pair_j < 0, n, pair_j)
|
|
409
|
+
else:
|
|
410
|
+
pair_i = pair_j = np.zeros(0, dtype=np.int64)
|
|
338
411
|
mb_rows = np.asarray(meas_box_rows if meas_box_rows is not None else [], dtype=np.int64)
|
|
339
412
|
if mb_rows.size:
|
|
340
413
|
if meas_box_lo is None or meas_box_hi is None:
|
|
@@ -386,7 +459,8 @@ def solve_ipm_pd(
|
|
|
386
459
|
)
|
|
387
460
|
|
|
388
461
|
tr_mask = np.ones(n, dtype=bool)
|
|
389
|
-
tr_mask[
|
|
462
|
+
tr_mask[var_box_idx] = False
|
|
463
|
+
prox_on = np.arange(nb) < n_var_box
|
|
390
464
|
tr_pos = np.flatnonzero(tr_mask)
|
|
391
465
|
limiters: dict[int, int] = {}
|
|
392
466
|
limiting_rows: dict[int, int] = {}
|
|
@@ -456,7 +530,9 @@ def solve_ipm_pd(
|
|
|
456
530
|
w_mat[eq_rows] = rho
|
|
457
531
|
w_mat[mb_rows] += d_k[nb:]
|
|
458
532
|
diag = np.zeros(n)
|
|
459
|
-
diag[box_idx] = d_k[:nb] +
|
|
533
|
+
diag[box_idx] = d_k[:nb] + np.where(
|
|
534
|
+
prox_on, max(eq_prox_abs, box_scale * w_median), eq_prox_abs
|
|
535
|
+
)
|
|
460
536
|
# Stationarity residual of the current point (for reporting).
|
|
461
537
|
y_dual = np.zeros(nb + mb_rows.size)
|
|
462
538
|
y_dual[il] += y_l
|
|
@@ -568,6 +644,14 @@ def solve_ipm_pd(
|
|
|
568
644
|
# The stopping test measures the Newton step, not how far the search cut it.
|
|
569
645
|
step_inf = a_p * free_inf
|
|
570
646
|
fit_inf = a_p * float(np.max(np.sqrt(w_data) * np.abs(hdx))) if m else 0.0
|
|
647
|
+
# The step limits shorten the step like the search below, after
|
|
648
|
+
# step_inf has measured the Newton step.
|
|
649
|
+
a_p = min(
|
|
650
|
+
a_p,
|
|
651
|
+
_step_cap(
|
|
652
|
+
dx, x, rel_idx, step_rel_max, abs_idx, step_abs_max, pair_i, pair_j, step_diff_max
|
|
653
|
+
),
|
|
654
|
+
)
|
|
571
655
|
a_full = a_p
|
|
572
656
|
r_new = None
|
|
573
657
|
# A direction that does not descend (inconsistent slacks of a nonlinear
|
|
@@ -55,6 +55,7 @@ from gridstate.state import (
|
|
|
55
55
|
flat_start,
|
|
56
56
|
flat_start_with_box,
|
|
57
57
|
pack,
|
|
58
|
+
tap_consistent_voltages,
|
|
58
59
|
unpack,
|
|
59
60
|
unpack_full,
|
|
60
61
|
)
|
|
@@ -120,7 +121,9 @@ def estimate(
|
|
|
120
121
|
``"ipm_pd"`` (the same problem by the primal-dual Mehrotra
|
|
121
122
|
predictor-corrector, :mod:`gridstate.algorithms.ipm_pd`).
|
|
122
123
|
init: initial guess strategy:
|
|
123
|
-
- ``"flat"`` —
|
|
124
|
+
- ``"flat"`` — δ=0 for all nodes, V consistent with the
|
|
125
|
+
transformer ratios (:func:`gridstate.state.tap_consistent_voltages`;
|
|
126
|
+
1 p.u. without off-nominal ratios);
|
|
124
127
|
- ``"results"`` — take the current ``voltage_magnitude``/``voltage_angle``
|
|
125
128
|
from ``model``;
|
|
126
129
|
- ``"slack"`` — V=V_slack for all nodes, δ=0.
|
|
@@ -651,7 +654,9 @@ def _initial_state_from_mode(
|
|
|
651
654
|
) -> np.ndarray:
|
|
652
655
|
"""``e_init`` of the ``init`` strategy alone."""
|
|
653
656
|
if init == "flat":
|
|
654
|
-
|
|
657
|
+
e = flat_start(layout)
|
|
658
|
+
e[layout.offset_v : layout.offset_v + layout.n_bus] = tap_consistent_voltages(network_pu)
|
|
659
|
+
return e
|
|
655
660
|
|
|
656
661
|
if init == "results":
|
|
657
662
|
nodes_arr = model.nodes.to_numpy()
|
|
@@ -684,6 +689,66 @@ def _initial_state_from_mode(
|
|
|
684
689
|
raise ValueError(f"Unknown init mode init={init!r}")
|
|
685
690
|
|
|
686
691
|
|
|
692
|
+
# Voltage guard of ipm_pd (``voltage_guard=True``): node V box in p.u. of the
|
|
693
|
+
# nominal voltage and the per-iteration step limits.
|
|
694
|
+
_VG_LO_MIN = 0.5 # voltage_min below this share of nominal is ignored ...
|
|
695
|
+
_VG_LO_FALLBACK = 0.55 # ... and replaced by this
|
|
696
|
+
_VG_HI_MAX = 2.0 # voltage_max above this share of nominal is ignored ...
|
|
697
|
+
_VG_HI_FALLBACK = 1.8 # ... and replaced by this
|
|
698
|
+
_VG_BOTH_UNSET = (0.7, 1.5) # neither limit set
|
|
699
|
+
_VG_V_STEP_REL = 0.2 # |αp·ΔV| ≤ 0.2·V
|
|
700
|
+
_VG_DELTA_STEP = 0.6 # |αp·Δδ| ≤ 0.6 rad
|
|
701
|
+
_VG_BRANCH_DELTA_STEP = 0.35 # |αp·(Δδ_i − Δδ_j)| ≤ 0.35 rad over a branch
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def _voltage_guard_kwargs(
|
|
705
|
+
model: Working, network_pu: NetworkPU, layout: StateLayout
|
|
706
|
+
) -> dict[str, Any]:
|
|
707
|
+
"""``solve_ipm_pd`` kwargs of the voltage guard: V boxes and step limits.
|
|
708
|
+
|
|
709
|
+
The box of a node comes from its ``voltage_min`` / ``voltage_max`` (kV): a
|
|
710
|
+
limit that is unset (0) or implausible (below half / above twice the
|
|
711
|
+
nominal) falls back to 0.55 / 1.8 p.u.; with neither limit set the box is
|
|
712
|
+
0.7…1.5 p.u. It keeps the iteration out of the collapsed region (V → 0,
|
|
713
|
+
where the node angle is undetermined); the step limits keep one Newton
|
|
714
|
+
step from crossing into it.
|
|
715
|
+
"""
|
|
716
|
+
nodes = model.nodes.to_numpy()
|
|
717
|
+
id_to_pos = id_to_pos_map(network_pu.bus_ids)
|
|
718
|
+
n = network_pu.n_bus
|
|
719
|
+
umin = np.zeros(n)
|
|
720
|
+
umax = np.zeros(n)
|
|
721
|
+
for row in nodes[nodes["status"]]:
|
|
722
|
+
pos = id_to_pos.get(int(row["id"]))
|
|
723
|
+
if pos is not None:
|
|
724
|
+
umin[pos] = float(row["voltage_min"])
|
|
725
|
+
umax[pos] = float(row["voltage_max"])
|
|
726
|
+
vn = np.asarray(network_pu.bus_vn_kv, dtype=np.float64)
|
|
727
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
728
|
+
lo_pu = np.where(vn > 0, umin / vn, 0.0)
|
|
729
|
+
hi_pu = np.where(vn > 0, umax / vn, 0.0)
|
|
730
|
+
lo = np.where((umin > 0) & (lo_pu >= _VG_LO_MIN), lo_pu, _VG_LO_FALLBACK)
|
|
731
|
+
hi = np.where((umax > 0) & (hi_pu <= _VG_HI_MAX), hi_pu, _VG_HI_FALLBACK)
|
|
732
|
+
unset = (umin <= 0) & (umax <= 0)
|
|
733
|
+
lo[unset], hi[unset] = _VG_BOTH_UNSET
|
|
734
|
+
bad = lo >= hi
|
|
735
|
+
lo[bad], hi[bad] = _VG_LO_FALLBACK, _VG_HI_FALLBACK
|
|
736
|
+
v_idx = np.arange(layout.offset_v, layout.offset_v + n, dtype=np.int64)
|
|
737
|
+
delta_of = np.full(n, -1, dtype=np.int64)
|
|
738
|
+
delta_of[layout.non_slack_idx] = layout.offset_delta + np.arange(n - 1)
|
|
739
|
+
return {
|
|
740
|
+
"state_box_idx": v_idx,
|
|
741
|
+
"state_box_lo": lo,
|
|
742
|
+
"state_box_hi": hi,
|
|
743
|
+
"step_rel_idx": v_idx,
|
|
744
|
+
"step_rel_max": _VG_V_STEP_REL,
|
|
745
|
+
"step_abs_idx": delta_of[layout.non_slack_idx],
|
|
746
|
+
"step_abs_max": _VG_DELTA_STEP,
|
|
747
|
+
"step_diff_pairs": (delta_of[network_pu.from_idx], delta_of[network_pu.to_idx]),
|
|
748
|
+
"step_diff_max": _VG_BRANCH_DELTA_STEP,
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
|
|
687
752
|
def _run_ipm(
|
|
688
753
|
model: Working,
|
|
689
754
|
network_pu: NetworkPU,
|
|
@@ -712,6 +777,7 @@ def _run_ipm(
|
|
|
712
777
|
role_estimates: dict[int, float] | None = None,
|
|
713
778
|
hint_iterations: int = 100,
|
|
714
779
|
huber_threshold: str = "fixed",
|
|
780
|
+
voltage_guard: bool = True,
|
|
715
781
|
**ipm_kwargs: Any,
|
|
716
782
|
) -> IPMResult:
|
|
717
783
|
"""IPM mode: extends the state vector with box-vars and solves the primal log-barrier WLS.
|
|
@@ -751,6 +817,10 @@ def _run_ipm(
|
|
|
751
817
|
than ``hint_iterations`` iterations (``0`` — no iteration trigger) logs a
|
|
752
818
|
WARNING with convergence hints (:mod:`gridstate.ipm_hints`).
|
|
753
819
|
|
|
820
|
+
``voltage_guard`` (``"ipm_pd"`` only, ignored by ``"ipm"``): node V boxes
|
|
821
|
+
from ``voltage_min`` / ``voltage_max`` and per-step limits on V/δ (see
|
|
822
|
+
:func:`_voltage_guard_kwargs`).
|
|
823
|
+
|
|
754
824
|
``huber_threshold`` (``"ipm_pd"`` only): ``"fixed"`` keeps the Huber
|
|
755
825
|
threshold at ``huber_c``; ``"adaptive"`` raises it to ``k·median|r/σ|``,
|
|
756
826
|
recomputed every iteration, so the estimate follows the solver path. The
|
|
@@ -866,6 +936,7 @@ def _run_ipm(
|
|
|
866
936
|
huber_warmup_iters=2,
|
|
867
937
|
huber_adaptive_k=0.0 if huber_threshold == "fixed" else 2.0,
|
|
868
938
|
kkt_solver=kkt,
|
|
939
|
+
**(_voltage_guard_kwargs(model, network_pu, layout_ipm) if voltage_guard else {}),
|
|
869
940
|
)
|
|
870
941
|
pd_result = result
|
|
871
942
|
if result.solve_status is not SolveStatus.KKT:
|
|
@@ -552,6 +552,16 @@ class PipelineConfig:
|
|
|
552
552
|
"min_value/max_value set is kept within [min_value, max_value] as an "
|
|
553
553
|
"inequality of the interior-point problem. Default OFF.",
|
|
554
554
|
)
|
|
555
|
+
ipm_voltage_guard: bool = _toggle(
|
|
556
|
+
True,
|
|
557
|
+
group=_G_IPM,
|
|
558
|
+
label="Voltage guard (ipm_pd)",
|
|
559
|
+
help="ipm_pd only (ignored otherwise): every node V is kept within a box "
|
|
560
|
+
"from its voltage_min/voltage_max (unset or implausible limits fall back "
|
|
561
|
+
"to 0.55/1.8 p.u. of nominal, 0.7..1.5 with neither set), and one step "
|
|
562
|
+
"changes V by at most 20 %, an angle by at most 0.6 rad and a branch "
|
|
563
|
+
"angle difference by at most 0.35 rad. Default ON.",
|
|
564
|
+
)
|
|
555
565
|
ipm_injection_roles: bool = _toggle(
|
|
556
566
|
False,
|
|
557
567
|
group=_G_IPM,
|
|
@@ -61,6 +61,7 @@ def _ipm_kwargs(cfg: PipelineConfig) -> dict:
|
|
|
61
61
|
"balance_tol_q_mvar": cfg.ipm_balance_tol_q_mvar,
|
|
62
62
|
"fixed_injection_from_input": cfg.ipm_fixed_injection_from_input,
|
|
63
63
|
"measurement_bounds": cfg.ipm_measurement_bounds,
|
|
64
|
+
"voltage_guard": cfg.ipm_voltage_guard,
|
|
64
65
|
"injection_roles": cfg.ipm_injection_roles,
|
|
65
66
|
"hint_iterations": cfg.ipm_hint_iterations,
|
|
66
67
|
"huber_threshold": cfg.ipm_huber_threshold,
|
|
@@ -24,9 +24,20 @@ The layout to/from a flat array is handled by ``pack`` / ``unpack``.
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
26
|
from dataclasses import dataclass, field
|
|
27
|
-
from typing import Any
|
|
27
|
+
from typing import TYPE_CHECKING, Any
|
|
28
28
|
|
|
29
29
|
import numpy as np
|
|
30
|
+
import scipy.sparse as sp
|
|
31
|
+
from scipy.sparse.linalg import spsolve
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from gridstate.units import NetworkPU
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Impedance floor of the tap-consistent start weights: breakers (|z| ≈ 0) bind
|
|
39
|
+
# their ends without making the system singular.
|
|
40
|
+
_TAP_START_Z_FLOOR = 1e-4
|
|
30
41
|
|
|
31
42
|
|
|
32
43
|
def _empty_int64() -> np.ndarray:
|
|
@@ -254,6 +265,46 @@ def flat_start(layout: StateLayout) -> np.ndarray:
|
|
|
254
265
|
return e
|
|
255
266
|
|
|
256
267
|
|
|
268
|
+
def tap_consistent_voltages(network_pu: NetworkPU) -> np.ndarray:
|
|
269
|
+
"""Flat-start magnitudes consistent with the transformer ratios (p.u.).
|
|
270
|
+
|
|
271
|
+
``V = 1`` everywhere contradicts every branch whose ratio in p.u. is not 1
|
|
272
|
+
(an off-nominal tap, or a node whose ``voltage_nominal`` does not match the
|
|
273
|
+
ratio of its transformer): on a low-impedance branch the flat start then
|
|
274
|
+
carries a circulating flow of hundreds of p.u. Instead ``log V`` solves the
|
|
275
|
+
least squares ``log V_to − log V_from = −log k`` over all branches, weighted
|
|
276
|
+
by ``1/|z|``, with ``log V_slack = 0``. On a radial transformer chain this
|
|
277
|
+
propagates the ratios exactly, in a meshed network it averages them.
|
|
278
|
+
A component without the slack is centred on 1.0.
|
|
279
|
+
"""
|
|
280
|
+
n = network_pu.n_bus
|
|
281
|
+
f = np.asarray(network_pu.from_idx, dtype=np.int64)
|
|
282
|
+
t = np.asarray(network_pu.to_idx, dtype=np.int64)
|
|
283
|
+
if f.size == 0:
|
|
284
|
+
return np.ones(n, dtype=np.float64)
|
|
285
|
+
k = np.asarray(network_pu.tap_ratio, dtype=np.float64)
|
|
286
|
+
k = np.where(np.isfinite(k) & (k > 0), k, 1.0)
|
|
287
|
+
z = np.hypot(network_pu.branch_r, network_pu.branch_x)
|
|
288
|
+
w = 1.0 / np.maximum(z, _TAP_START_Z_FLOOR)
|
|
289
|
+
rows = np.arange(f.size)
|
|
290
|
+
a = sp.csr_matrix(
|
|
291
|
+
(np.r_[np.ones(f.size), -np.ones(f.size)], (np.r_[rows, rows], np.r_[t, f])),
|
|
292
|
+
shape=(f.size, n),
|
|
293
|
+
)
|
|
294
|
+
# A vanishing pull to log V = 0 keeps nodes outside the slack's component
|
|
295
|
+
# determined without moving the connected ones.
|
|
296
|
+
lap = (a.T @ sp.diags(w) @ a + sp.identity(n) * (1e-9 * float(w.mean()))).tolil()
|
|
297
|
+
rhs = a.T @ (-w * np.log(k))
|
|
298
|
+
s = network_pu.slack_idx
|
|
299
|
+
lap[s, :] = 0.0
|
|
300
|
+
lap[s, s] = 1.0
|
|
301
|
+
rhs[s] = 0.0
|
|
302
|
+
log_v = np.asarray(spsolve(lap.tocsc(), rhs), dtype=np.float64)
|
|
303
|
+
if not np.all(np.isfinite(log_v)):
|
|
304
|
+
return np.ones(n, dtype=np.float64)
|
|
305
|
+
return np.exp(log_v)
|
|
306
|
+
|
|
307
|
+
|
|
257
308
|
def unpack_full(
|
|
258
309
|
e: np.ndarray,
|
|
259
310
|
layout: StateLayout,
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
"scalars": {
|
|
5
5
|
"success": true,
|
|
6
6
|
"iterations": 3,
|
|
7
|
-
"objective_value": 845.
|
|
7
|
+
"objective_value": 845.3285898461884,
|
|
8
8
|
"algorithm": "wls",
|
|
9
9
|
"convergence_status": "converged",
|
|
10
10
|
"chi2": {
|
|
11
|
-
"value": 845.
|
|
11
|
+
"value": 845.3285898461886,
|
|
12
12
|
"dof": 863,
|
|
13
13
|
"threshold": 932.4536984554023,
|
|
14
14
|
"passes": true
|
|
@@ -267,9 +267,9 @@
|
|
|
267
267
|
"algorithm": "wls",
|
|
268
268
|
"success": true,
|
|
269
269
|
"iterations": 3,
|
|
270
|
-
"objective_value": 845.
|
|
270
|
+
"objective_value": 845.3285898461884,
|
|
271
271
|
"solve_status": "kkt",
|
|
272
|
-
"objective_wls": 845.
|
|
272
|
+
"objective_wls": 845.3285898461886
|
|
273
273
|
}
|
|
274
274
|
},
|
|
275
275
|
{
|
|
@@ -323,11 +323,11 @@
|
|
|
323
323
|
"scalars": {
|
|
324
324
|
"success": true,
|
|
325
325
|
"iterations": 9,
|
|
326
|
-
"objective_value": 395.
|
|
326
|
+
"objective_value": 395.297085057761,
|
|
327
327
|
"algorithm": "ipm",
|
|
328
328
|
"convergence_status": "kkt",
|
|
329
329
|
"chi2": {
|
|
330
|
-
"value": 789.
|
|
330
|
+
"value": 789.2957682480284,
|
|
331
331
|
"dof": 863,
|
|
332
332
|
"threshold": 932.4536984554023,
|
|
333
333
|
"passes": true
|
|
@@ -587,9 +587,9 @@
|
|
|
587
587
|
"algorithm": "ipm",
|
|
588
588
|
"success": true,
|
|
589
589
|
"iterations": 9,
|
|
590
|
-
"objective_value": 300.
|
|
590
|
+
"objective_value": 300.22040484190455,
|
|
591
591
|
"solve_status": "mu_schedule_done",
|
|
592
|
-
"objective_wls": 601.
|
|
592
|
+
"objective_wls": 601.3973601055147
|
|
593
593
|
}
|
|
594
594
|
},
|
|
595
595
|
{
|
|
@@ -652,7 +652,7 @@
|
|
|
652
652
|
"success": true,
|
|
653
653
|
"iterations": 9,
|
|
654
654
|
"solve_status": "kkt",
|
|
655
|
-
"objective_wls": 789.
|
|
655
|
+
"objective_wls": 789.2957682480284
|
|
656
656
|
}
|
|
657
657
|
},
|
|
658
658
|
{
|
|
@@ -676,11 +676,11 @@
|
|
|
676
676
|
"scalars": {
|
|
677
677
|
"success": true,
|
|
678
678
|
"iterations": 9,
|
|
679
|
-
"objective_value": 395.
|
|
679
|
+
"objective_value": 395.297085057761,
|
|
680
680
|
"algorithm": "ipm",
|
|
681
681
|
"convergence_status": "kkt",
|
|
682
682
|
"chi2": {
|
|
683
|
-
"value": 789.
|
|
683
|
+
"value": 789.2957682480284,
|
|
684
684
|
"dof": 863,
|
|
685
685
|
"threshold": 932.4536984554023,
|
|
686
686
|
"passes": true
|
|
@@ -940,9 +940,9 @@
|
|
|
940
940
|
"algorithm": "ipm",
|
|
941
941
|
"success": true,
|
|
942
942
|
"iterations": 9,
|
|
943
|
-
"objective_value": 300.
|
|
943
|
+
"objective_value": 300.22040484190455,
|
|
944
944
|
"solve_status": "mu_schedule_done",
|
|
945
|
-
"objective_wls": 601.
|
|
945
|
+
"objective_wls": 601.3973601055147
|
|
946
946
|
}
|
|
947
947
|
},
|
|
948
948
|
{
|
|
@@ -970,7 +970,7 @@
|
|
|
970
970
|
"success": true,
|
|
971
971
|
"iterations": 9,
|
|
972
972
|
"solve_status": "kkt",
|
|
973
|
-
"objective_wls": 789.
|
|
973
|
+
"objective_wls": 789.2957682480284
|
|
974
974
|
}
|
|
975
975
|
},
|
|
976
976
|
{
|
|
@@ -1022,11 +1022,11 @@
|
|
|
1022
1022
|
"scalars": {
|
|
1023
1023
|
"success": true,
|
|
1024
1024
|
"iterations": 9,
|
|
1025
|
-
"objective_value": 395.
|
|
1025
|
+
"objective_value": 395.297085057761,
|
|
1026
1026
|
"algorithm": "ipm",
|
|
1027
1027
|
"convergence_status": "kkt",
|
|
1028
1028
|
"chi2": {
|
|
1029
|
-
"value": 789.
|
|
1029
|
+
"value": 789.2957682480284,
|
|
1030
1030
|
"dof": 863,
|
|
1031
1031
|
"threshold": 932.4536984554023,
|
|
1032
1032
|
"passes": true
|
|
@@ -1286,9 +1286,9 @@
|
|
|
1286
1286
|
"algorithm": "ipm",
|
|
1287
1287
|
"success": true,
|
|
1288
1288
|
"iterations": 9,
|
|
1289
|
-
"objective_value": 300.
|
|
1289
|
+
"objective_value": 300.22040484190455,
|
|
1290
1290
|
"solve_status": "mu_schedule_done",
|
|
1291
|
-
"objective_wls": 601.
|
|
1291
|
+
"objective_wls": 601.3973601055147
|
|
1292
1292
|
}
|
|
1293
1293
|
},
|
|
1294
1294
|
{
|
|
@@ -1351,7 +1351,7 @@
|
|
|
1351
1351
|
"success": true,
|
|
1352
1352
|
"iterations": 9,
|
|
1353
1353
|
"solve_status": "kkt",
|
|
1354
|
-
"objective_wls": 789.
|
|
1354
|
+
"objective_wls": 789.2957682480284
|
|
1355
1355
|
}
|
|
1356
1356
|
},
|
|
1357
1357
|
{
|
|
@@ -1385,11 +1385,11 @@
|
|
|
1385
1385
|
"scalars": {
|
|
1386
1386
|
"success": true,
|
|
1387
1387
|
"iterations": 9,
|
|
1388
|
-
"objective_value": 395.
|
|
1388
|
+
"objective_value": 395.297085057761,
|
|
1389
1389
|
"algorithm": "ipm",
|
|
1390
1390
|
"convergence_status": "kkt",
|
|
1391
1391
|
"chi2": {
|
|
1392
|
-
"value": 789.
|
|
1392
|
+
"value": 789.2957682480284,
|
|
1393
1393
|
"dof": 863,
|
|
1394
1394
|
"threshold": 932.4536984554023,
|
|
1395
1395
|
"passes": true
|
|
@@ -1649,9 +1649,9 @@
|
|
|
1649
1649
|
"algorithm": "ipm",
|
|
1650
1650
|
"success": true,
|
|
1651
1651
|
"iterations": 9,
|
|
1652
|
-
"objective_value": 300.
|
|
1652
|
+
"objective_value": 300.22040484190455,
|
|
1653
1653
|
"solve_status": "mu_schedule_done",
|
|
1654
|
-
"objective_wls": 601.
|
|
1654
|
+
"objective_wls": 601.3973601055147
|
|
1655
1655
|
}
|
|
1656
1656
|
},
|
|
1657
1657
|
{
|
|
@@ -1714,7 +1714,7 @@
|
|
|
1714
1714
|
"success": true,
|
|
1715
1715
|
"iterations": 9,
|
|
1716
1716
|
"solve_status": "kkt",
|
|
1717
|
-
"objective_wls": 789.
|
|
1717
|
+
"objective_wls": 789.2957682480284
|
|
1718
1718
|
}
|
|
1719
1719
|
},
|
|
1720
1720
|
{
|
|
@@ -1747,11 +1747,11 @@
|
|
|
1747
1747
|
"scalars": {
|
|
1748
1748
|
"success": true,
|
|
1749
1749
|
"iterations": 9,
|
|
1750
|
-
"objective_value": 395.
|
|
1750
|
+
"objective_value": 395.29708505776426,
|
|
1751
1751
|
"algorithm": "ipm",
|
|
1752
1752
|
"convergence_status": "kkt",
|
|
1753
1753
|
"chi2": {
|
|
1754
|
-
"value": 789.
|
|
1754
|
+
"value": 789.2957682440822,
|
|
1755
1755
|
"dof": 863,
|
|
1756
1756
|
"threshold": 932.4536984554023,
|
|
1757
1757
|
"passes": true
|
|
@@ -2015,9 +2015,9 @@
|
|
|
2015
2015
|
"algorithm": "ipm",
|
|
2016
2016
|
"success": true,
|
|
2017
2017
|
"iterations": 9,
|
|
2018
|
-
"objective_value": 301.
|
|
2018
|
+
"objective_value": 301.3328623053196,
|
|
2019
2019
|
"solve_status": "kkt",
|
|
2020
|
-
"objective_wls": 601.
|
|
2020
|
+
"objective_wls": 601.3742571933731
|
|
2021
2021
|
}
|
|
2022
2022
|
},
|
|
2023
2023
|
{
|
|
@@ -2080,7 +2080,7 @@
|
|
|
2080
2080
|
"success": true,
|
|
2081
2081
|
"iterations": 9,
|
|
2082
2082
|
"solve_status": "kkt",
|
|
2083
|
-
"objective_wls": 789.
|
|
2083
|
+
"objective_wls": 789.2957682440822
|
|
2084
2084
|
}
|
|
2085
2085
|
},
|
|
2086
2086
|
{
|
|
Binary file
|