gridstate 2.1.0__tar.gz → 2.3.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.1.0 → gridstate-2.3.0}/CHANGELOG.md +63 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/PKG-INFO +1 -1
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/_version.py +3 -3
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algorithms/ipm.py +109 -1
- gridstate-2.3.0/gridstate/algorithms/ipm_pd.py +617 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algorithms/kkt_solver.py +59 -18
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/api.py +180 -41
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/constants.py +11 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/derived.py +9 -1
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/derived_tables.py +38 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/version.py +3 -1
- gridstate-2.3.0/gridstate/ipm_hints.py +282 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/config.py +94 -4
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/passes.py +10 -2
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/preparation.py +8 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/post_processing.py +19 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/ipm_setup.py +321 -8
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/result.py +1 -1
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/apply_resolved.py +137 -26
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/z_vector.py +47 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/PKG-INFO +1 -1
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/SOURCES.txt +7 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/scm_file_list.json +7 -0
- gridstate-2.3.0/gridstate.egg-info/scm_version.json +8 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_derived_tables.py +6 -0
- gridstate-2.3.0/tests/test_ipm_balance_modes.py +202 -0
- gridstate-2.3.0/tests/test_ipm_hints.py +113 -0
- gridstate-2.3.0/tests/test_ipm_injection_roles.py +275 -0
- gridstate-2.3.0/tests/test_ipm_pd.py +247 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline_config_validation.py +1 -1
- gridstate-2.3.0/tests/test_telemetry_bounds.py +86 -0
- gridstate-2.1.0/gridstate.egg-info/scm_version.json +0 -8
- {gridstate-2.1.0 → gridstate-2.3.0}/.github/workflows/ci.yml +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/.github/workflows/release.yml +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/.gitignore +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/.pre-commit-config.yaml +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/LICENSE +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/Makefile +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/README.md +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/build_cython.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/__init__.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/__pyinstaller/__init__.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/__pyinstaller/hook-gridstate.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/_arrays.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/adapters/__init__.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/adapters/from_pandapower.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algebra/__init__.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algebra/_selinv_kernels.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algebra/base.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algebra/estimators.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algebra/jacobian_pattern.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algebra/normalized_residuals.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algorithms/__init__.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algorithms/_gram_numba.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algorithms/status.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algorithms/wls.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/bad_data_repass.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/bounds.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/__init__.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/_legacy.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/containers.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/serialize.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/tables.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/validate.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/losses.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/measurement_ids.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/__init__.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/context.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/manifest.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/orchestration.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/runner.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/steps.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/__init__.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/_scan.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/meas_rows.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/mirror_voltage.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/node_props.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/pseudo_measurements.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/py.typed +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/quality_summary.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/resolve_pass.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/runtime.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/shunt_sanity.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/state.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/__init__.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/_filters.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/generators.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/kinds.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/load_only_injection.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/loss_filter.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/measurements.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/noise.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/on_line.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/quality.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/rpn.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/shunts.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/sigma_floor.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/units.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/voltage_filter.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/voltage_nominal.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/topology.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/units.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/utils.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/v_mirror.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/v_refine.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/validation/__init__.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/validation/_diagnostics.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/validation/bad_data.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/validation/chi2_test.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/validation/observability.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/working.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/ybus.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/dependency_links.txt +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/entry_points.txt +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/requires.txt +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/top_level.txt +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/pyproject.toml +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/setup.cfg +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/__init__.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/_golden.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/conftest.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_adapters_lazy_import.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_aggregate_generators.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_algebra_base.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_anti_overshoot.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_arrays_helpers.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_bad_data.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_bad_data_repass.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_bounds.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_boxed_balance.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_breaker_reactance.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_chain_mirror.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_contract.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_contract_edge_cases.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_contract_runtime.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case118.json +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case118.npz +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case118_degraded.json +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case118_degraded.npz +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case14.json +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case14.npz +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case14_degraded.json +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case14_degraded.npz +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case30.json +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case30.npz +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case30_degraded.json +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case30_degraded.npz +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/manifest.json +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/ieee/case118.npz +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/ieee/case118_truth.npz +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/ieee/case14.npz +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/ieee/case14_truth.npz +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/ieee/case30.npz +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/ieee/case30_truth.npz +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_enum_codes.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_flow_sigma_floor.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_from_pandapower.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_generator_shunt_kernels.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_golden.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_gram_numba.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_ieee_cases.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_ipm.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_ipm_integration.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_jacobian_pattern.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_kkt_solver.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_load_only_injection.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_losses_breakdown.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_losses_q_convention.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_losses_trafo_classification.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_materialize_kernel.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_measurement_filter_kernels.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_measurement_ids.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_mirror_voltage_kernel.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_near_critical_ranking.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_neutral_wording.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_noise_model.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_normalized_residuals.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_numpy_only_runtime.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_observability.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_on_line_kernel.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_perf_synthetic_network.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline_idempotent.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline_invariant_errors.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline_step_order.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline_warm_start.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline_working_layer.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_post_processing.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_preparation_steps.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_prepare_network.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pseudo_unknown_load.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pseudo_v_unobservable.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_quality_summary_paths.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_reconcile_balance.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_refine_merge.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_resolve_pass.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_result_quality_summary.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_rn_selected_inversion.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_serialize_npz.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_shunt_sanity.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_solve_status.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_state.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_state_range.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_telemetry_apply_kernel.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_telemetry_physics_kernels.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_tm_code_quality.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_topology_kernels.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_topology_port.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_transformer_from_shunt.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_units.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_units_array_kernels.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_v_mirror.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_v_refine.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_variance_charging.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_version.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_voltage_calibration_gen_nodes.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_voltage_range_filter_fallback.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_wls.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_wls_robustness.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_working_container.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_ybus.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_z_vector.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tools/gen_ieee_fixtures.py +0 -0
- {gridstate-2.1.0 → gridstate-2.3.0}/tools/golden.py +0 -0
|
@@ -9,6 +9,69 @@ 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.3.0 (2026-10-17)
|
|
13
|
+
|
|
14
|
+
### Behavior changes
|
|
15
|
+
|
|
16
|
+
- `ipm_pd` uses a fixed Huber threshold `c_eff = huber_c` by default
|
|
17
|
+
(`ipm_huber_threshold="fixed"`). The previous per-iteration adaptive raise
|
|
18
|
+
made the estimate depend on the solver path; `"adaptive"` restores it. On
|
|
19
|
+
regional models the quality is unchanged; `ipm_pd` estimates move slightly
|
|
20
|
+
(≤ 6e-5 p.u. V on the test models). `wls` / `ipm` are unaffected (#111)
|
|
21
|
+
- `ipm_pd` selects its step by Armijo backtracking on a merit function
|
|
22
|
+
(barrier objective, augmented Lagrangian of the equality rows, slack
|
|
23
|
+
penalty). It now converges on weakly determined cases (e.g. unmeasured
|
|
24
|
+
units with a wide box under `injection_roles`) that needed the primal
|
|
25
|
+
fallback; `ipm_pd` estimates can move slightly. The pipeline's `ipm_pd`
|
|
26
|
+
iteration limit is 300 (100 under a hard balance); the solve stops after
|
|
27
|
+
`max_search_failures` iterations without merit decrease (#106)
|
|
28
|
+
|
|
29
|
+
### Feat
|
|
30
|
+
|
|
31
|
+
- Convergence hints: a solve that does not converge, falls back from
|
|
32
|
+
`ipm_pd`, or takes more than `ipm_hint_iterations` iterations with a
|
|
33
|
+
dominant step limiter logs a WARNING naming the limiting nodes / variables
|
|
34
|
+
(with a suggestion), loose unmeasured box variables, the largest normalized
|
|
35
|
+
residuals and part measurements outside their range. `IPMResult` gains
|
|
36
|
+
`step_limiters` / `limiting_rows` (#110)
|
|
37
|
+
|
|
38
|
+
## v2.2.0 (2026-10-17)
|
|
39
|
+
|
|
40
|
+
All new behaviour is opt-in; with the defaults the results are unchanged.
|
|
41
|
+
|
|
42
|
+
### Feat
|
|
43
|
+
|
|
44
|
+
- `algorithm="ipm_pd"`: Mehrotra predictor-corrector interior-point solver;
|
|
45
|
+
predictor and corrector share one factorization per iteration
|
|
46
|
+
(`KKTSolver.factor_normal`). About 5× less solver time than `ipm` on large
|
|
47
|
+
models, with closely matching estimates. A solve that does not reach its
|
|
48
|
+
KKT test is redone by the primal IPM from the same start (#103)
|
|
49
|
+
- `balance_mode="hard"` (`ipm_balance_mode`): node balance rows become
|
|
50
|
+
equality constraints in both IPM solvers (method of multipliers,
|
|
51
|
+
quasi-definite step) with P / Q tolerances `balance_tol_p_mw` /
|
|
52
|
+
`balance_tol_q_mvar`. Experimental: it does not converge on every real
|
|
53
|
+
model yet (#103)
|
|
54
|
+
- `fixed_injection_from_input` (`ipm_fixed_injection_from_input`): the input
|
|
55
|
+
injection of node parts without a box variable enters the balance as a
|
|
56
|
+
fixed term. Experimental (#103)
|
|
57
|
+
- `measurement_bounds` (`ipm_measurement_bounds`, `ipm_pd` only):
|
|
58
|
+
`min_value` / `max_value` bound the estimated value of any measurement as
|
|
59
|
+
an inequality (#103)
|
|
60
|
+
- `injection_roles` (`ipm_injection_roles`, `ipm` / `ipm_pd`): node injection
|
|
61
|
+
rows tagged `source_code` `"inj_gen"` / `"inj_load"`
|
|
62
|
+
(`SOURCE_INJ_GEN` / `SOURCE_INJ_LOAD`) measure the generation / load box
|
|
63
|
+
variable directly; the node box is narrowed by the measurement box, the
|
|
64
|
+
unmeasured part of a measured node gets a prior, and the net row stays only
|
|
65
|
+
where a measured part has no box variable. The telemetry kernel emits the
|
|
66
|
+
part rows when the mode is on; `release_load_only_injections` and
|
|
67
|
+
`widen_generation_only_injections` are skipped in it (#103)
|
|
68
|
+
- `DerivedInputs.telemetry_bounds` (derived table `telemetry_bounds`):
|
|
69
|
+
optional plausible ranges of telemetry values, written by the telemetry
|
|
70
|
+
kernel into `min_value` / `max_value` (summed over components for
|
|
71
|
+
injection rows, generation minus load for the net row). Data contract
|
|
72
|
+
3.1.0, additive (#103)
|
|
73
|
+
- `ipm_balance_weight_factor` accepts values up to 1e6 (was 10) (#103)
|
|
74
|
+
|
|
12
75
|
## v2.1.0 (2026-10-14)
|
|
13
76
|
|
|
14
77
|
### Feat
|
|
@@ -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.3.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 3, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'gef79dd7f7'
|
|
@@ -33,6 +33,12 @@ changing the sparse structure. The step is limited to ``α_max·(1−ε)`` so th
|
|
|
33
33
|
|
|
34
34
|
Sanity property: with ``box_idx=[]`` the IPM is equivalent to Gauss-Newton WLS
|
|
35
35
|
(the barrier is zero, the outer loop makes a single pass).
|
|
36
|
+
|
|
37
|
+
Equality rows (``eq_idx``, e.g. hard nodal balance) are handled by the method
|
|
38
|
+
of multipliers: such a row ``c = h − z = −r`` enters Φ as
|
|
39
|
+
``½ρ (r − λ/ρ)²`` (``ρ`` — its ``r_inv_diag`` entry, ``λ`` — its multiplier),
|
|
40
|
+
and after every outer step ``λ ← λ + ρ·c``. At the fixed point ``c = 0``
|
|
41
|
+
exactly, whatever ``ρ``; the Newton systems stay the normal equations.
|
|
36
42
|
"""
|
|
37
43
|
|
|
38
44
|
from __future__ import annotations
|
|
@@ -40,7 +46,7 @@ from __future__ import annotations
|
|
|
40
46
|
import logging
|
|
41
47
|
import math
|
|
42
48
|
from collections.abc import Callable
|
|
43
|
-
from dataclasses import dataclass
|
|
49
|
+
from dataclasses import dataclass, field
|
|
44
50
|
|
|
45
51
|
import numpy as np
|
|
46
52
|
from scipy.sparse import csr_matrix
|
|
@@ -98,6 +104,28 @@ class IPMResult:
|
|
|
98
104
|
message: str = ""
|
|
99
105
|
status: str = "error"
|
|
100
106
|
solve_status: SolveStatus = SolveStatus.ERROR
|
|
107
|
+
# max |c| over the equality rows at the final point (p.u.); nan without them.
|
|
108
|
+
eq_inf_final: float = float("nan")
|
|
109
|
+
# Diagnostics of slow solves (see :mod:`gridstate.ipm_hints`): state index →
|
|
110
|
+
# number of iterations whose step that coordinate limited (it set the
|
|
111
|
+
# trust-region clip or the boundary rule, or dominated a step the line
|
|
112
|
+
# search cut).
|
|
113
|
+
step_limiters: dict[int, int] = field(default_factory=dict)
|
|
114
|
+
# ipm_pd only: measurement-box row → iterations whose step its bound limited.
|
|
115
|
+
limiting_rows: dict[int, int] = field(default_factory=dict)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _count(counts: dict[int, int], key: int) -> None:
|
|
119
|
+
counts[key] = counts.get(key, 0) + 1
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _blocking(cur: np.ndarray, step: np.ndarray, lo: np.ndarray, hi: np.ndarray) -> int:
|
|
123
|
+
"""Position of the coordinate that first reaches its bound along ``step`` (−1: none)."""
|
|
124
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
125
|
+
ratio = np.where(step < 0, (lo - cur) / step, np.where(step > 0, (hi - cur) / step, np.inf))
|
|
126
|
+
ratio = np.where(ratio > 0, ratio, np.inf)
|
|
127
|
+
k = int(np.argmin(ratio)) if ratio.size else -1
|
|
128
|
+
return k if k >= 0 and np.isfinite(ratio[k]) else -1
|
|
101
129
|
|
|
102
130
|
|
|
103
131
|
def _project_to_interior(
|
|
@@ -269,6 +297,10 @@ def solve_ipm(
|
|
|
269
297
|
huber_adaptive_k: float = 6.0,
|
|
270
298
|
huber_warmup_iters: int = 5,
|
|
271
299
|
kkt_solver: KKTSolver | None = None,
|
|
300
|
+
eq_idx: np.ndarray | None = None,
|
|
301
|
+
eq_tol: np.ndarray | None = None,
|
|
302
|
+
eq_extra_outer: int = 10,
|
|
303
|
+
eq_prox: float = 1e-9,
|
|
272
304
|
) -> IPMResult:
|
|
273
305
|
"""Primal log-barrier IPM for box-constrained WLS.
|
|
274
306
|
|
|
@@ -331,6 +363,17 @@ def solve_ipm(
|
|
|
331
363
|
symbolic factorization between iterations (see
|
|
332
364
|
``gridstate.algorithms.kkt_solver``). ``None`` — scipy spsolve
|
|
333
365
|
(the former behaviour, bit for bit).
|
|
366
|
+
eq_idx: rows of ``r`` that are equality constraints ``h = z`` (method of
|
|
367
|
+
multipliers, see the module docstring). ``None`` — none.
|
|
368
|
+
eq_tol: admissible ``|c|`` per row of ``eq_idx`` (p.u.). Once the μ
|
|
369
|
+
schedule is done, up to ``eq_extra_outer`` further outer steps at
|
|
370
|
+
``μ_min`` update the multipliers until every row is within it.
|
|
371
|
+
eq_prox: with equality rows, a proximal term ``eq_prox·max(ρ)`` added to
|
|
372
|
+
the diagonal of the box coordinates of the Newton matrix. Equalities
|
|
373
|
+
on the net injection leave the common level of a node's generation
|
|
374
|
+
and load to the weak barrier curvature, which makes the matrix
|
|
375
|
+
numerically singular; the term shortens such steps only and does not
|
|
376
|
+
move the stationary point.
|
|
334
377
|
|
|
335
378
|
Returns:
|
|
336
379
|
``IPMResult``.
|
|
@@ -354,6 +397,23 @@ def solve_ipm(
|
|
|
354
397
|
raise ValueError("box_lo/box_hi contain pairs with lo >= hi")
|
|
355
398
|
|
|
356
399
|
has_box = box_idx.size > 0
|
|
400
|
+
eq_rows = np.asarray(eq_idx, dtype=np.int64) if eq_idx is not None else None
|
|
401
|
+
has_eq = eq_rows is not None and eq_rows.size > 0
|
|
402
|
+
if has_eq:
|
|
403
|
+
assert eq_rows is not None
|
|
404
|
+
if eq_tol is None or np.shape(eq_tol) != eq_rows.shape:
|
|
405
|
+
raise ValueError("eq_tol must give one tolerance per row of eq_idx")
|
|
406
|
+
eq_tol_arr = np.asarray(eq_tol, dtype=np.float64)
|
|
407
|
+
# Target shift λ/ρ of the equality rows; residuals below are r − shift.
|
|
408
|
+
eq_shift = np.zeros(eq_rows.size, dtype=np.float64)
|
|
409
|
+
raw_residual_fn = residual_fn
|
|
410
|
+
eq_prox_diag = np.zeros(x_init.shape[0], dtype=np.float64)
|
|
411
|
+
eq_prox_diag[box_idx] = eq_prox * float(np.max(r_inv_diag[eq_rows]))
|
|
412
|
+
|
|
413
|
+
def residual_fn(x: np.ndarray) -> np.ndarray:
|
|
414
|
+
r_eff: np.ndarray = np.array(raw_residual_fn(x), dtype=np.float64)
|
|
415
|
+
r_eff[eq_rows] -= eq_shift
|
|
416
|
+
return r_eff
|
|
357
417
|
|
|
358
418
|
solver = kkt_solver if kkt_solver is not None else KKTSolver("scipy")
|
|
359
419
|
|
|
@@ -399,6 +459,8 @@ def solve_ipm(
|
|
|
399
459
|
mu = 0.0
|
|
400
460
|
|
|
401
461
|
outer_iter = 0
|
|
462
|
+
eq_extra = 0
|
|
463
|
+
eq_inf = float("nan")
|
|
402
464
|
schedule_done = False
|
|
403
465
|
# Termination status only (never fed back into the iteration).
|
|
404
466
|
outer_exhausted = False
|
|
@@ -408,6 +470,15 @@ def solve_ipm(
|
|
|
408
470
|
# it, so the next iteration (and the final objective) reuse it instead of
|
|
409
471
|
# calling residual_fn again at the same point.
|
|
410
472
|
r_at_x: np.ndarray | None = None
|
|
473
|
+
limiters: dict[int, int] = {}
|
|
474
|
+
tr_pos = np.flatnonzero(tr_mask) if tr_mask is not None else None
|
|
475
|
+
|
|
476
|
+
def _dominant(step: np.ndarray) -> int:
|
|
477
|
+
"""State index of the largest step component inside the trust region."""
|
|
478
|
+
if tr_pos is None:
|
|
479
|
+
return int(np.argmax(np.abs(step)))
|
|
480
|
+
return int(tr_pos[np.argmax(np.abs(step[tr_pos]))]) if tr_pos.size else 0
|
|
481
|
+
|
|
411
482
|
for outer_iter in range(outer_max):
|
|
412
483
|
any_inner_step = False # Lazy outer: if no step was made → break.
|
|
413
484
|
# inner exited by tolerance (grad/step < inner_tol) — a stationary
|
|
@@ -442,6 +513,8 @@ def solve_ipm(
|
|
|
442
513
|
grad_data = -np.asarray(HtRinv @ r, dtype=np.float64).ravel()
|
|
443
514
|
|
|
444
515
|
grad_b, hess_b = _barrier_grad_hess(x, box_idx, box_lo, box_hi, mu)
|
|
516
|
+
if has_eq:
|
|
517
|
+
hess_b = hess_b + eq_prox_diag
|
|
445
518
|
grad = grad_data + grad_b
|
|
446
519
|
grad_inf = float(np.max(np.abs(grad))) if grad.size else 0.0
|
|
447
520
|
|
|
@@ -492,6 +565,7 @@ def solve_ipm(
|
|
|
492
565
|
step_inf_raw = float(np.max(np.abs(dx_tr))) if dx_tr.size else 0.0
|
|
493
566
|
if step_inf_raw > tr_radius:
|
|
494
567
|
dx = dx * (tr_radius / step_inf_raw)
|
|
568
|
+
_count(limiters, _dominant(dx))
|
|
495
569
|
step_inf = float(np.max(np.abs(dx))) if dx.size else 0.0
|
|
496
570
|
if step_inf < inner_tol:
|
|
497
571
|
inner_converged = True
|
|
@@ -507,6 +581,11 @@ def solve_ipm(
|
|
|
507
581
|
fraction_to_boundary,
|
|
508
582
|
)
|
|
509
583
|
alpha = min(alpha_max, 1.0) if math.isfinite(alpha_max) else 1.0
|
|
584
|
+
if alpha < 1.0:
|
|
585
|
+
k = _blocking(x[box_idx], dx[box_idx], box_lo, box_hi)
|
|
586
|
+
if k >= 0:
|
|
587
|
+
_count(limiters, int(box_idx[k]))
|
|
588
|
+
alpha_first = alpha
|
|
510
589
|
|
|
511
590
|
phi_cur = 0.5 * float(r @ (R_inv @ r)) + _barrier_value(
|
|
512
591
|
x,
|
|
@@ -540,6 +619,8 @@ def solve_ipm(
|
|
|
540
619
|
break
|
|
541
620
|
|
|
542
621
|
iter_inner_total += 1
|
|
622
|
+
if not accepted or alpha < alpha_first:
|
|
623
|
+
_count(limiters, _dominant(dx))
|
|
543
624
|
if not accepted:
|
|
544
625
|
inner_stalled = True
|
|
545
626
|
# No admissible step found — the outer loop will increase μ and try again.
|
|
@@ -552,6 +633,24 @@ def solve_ipm(
|
|
|
552
633
|
r_at_x = r_accepted
|
|
553
634
|
any_inner_step = True
|
|
554
635
|
|
|
636
|
+
if has_eq:
|
|
637
|
+
assert eq_rows is not None and r_at_x is not None
|
|
638
|
+
# Constraint value c = −r_raw = −(r_eff + shift); λ/ρ ← λ/ρ + c.
|
|
639
|
+
raw_eq = r_at_x[eq_rows] + eq_shift
|
|
640
|
+
eq_inf = float(np.max(np.abs(raw_eq)))
|
|
641
|
+
eq_ok = bool(np.all(np.abs(raw_eq) <= eq_tol_arr))
|
|
642
|
+
if (mu <= mu_min or not has_box) and (eq_ok or eq_extra >= eq_extra_outer):
|
|
643
|
+
schedule_done = True
|
|
644
|
+
break
|
|
645
|
+
if not any_inner_step and not inner_converged:
|
|
646
|
+
break # stuck: the multiplier update would not move x either
|
|
647
|
+
eq_shift = eq_shift - raw_eq
|
|
648
|
+
r_at_x = r_at_x.copy()
|
|
649
|
+
r_at_x[eq_rows] = raw_eq - eq_shift
|
|
650
|
+
if mu <= mu_min or not has_box:
|
|
651
|
+
eq_extra += 1
|
|
652
|
+
continue
|
|
653
|
+
|
|
555
654
|
if not has_box:
|
|
556
655
|
break # WLS mode: a single outer pass.
|
|
557
656
|
|
|
@@ -575,6 +674,11 @@ def solve_ipm(
|
|
|
575
674
|
|
|
576
675
|
# Final residual/objective
|
|
577
676
|
r_final = r_at_x if r_at_x is not None else residual_fn(x)
|
|
677
|
+
if has_eq:
|
|
678
|
+
assert eq_rows is not None
|
|
679
|
+
r_final = r_final.copy()
|
|
680
|
+
r_final[eq_rows] += eq_shift # report the true residual of the equality rows
|
|
681
|
+
eq_inf = float(np.max(np.abs(r_final[eq_rows])))
|
|
578
682
|
obj_data = 0.5 * float(r_final @ (R_inv @ r_final))
|
|
579
683
|
|
|
580
684
|
kkt = grad_inf < max(1e-2, inner_tol * 100)
|
|
@@ -596,6 +700,8 @@ def solve_ipm(
|
|
|
596
700
|
f"outer={outer_iter + 1}, inner_total={iter_inner_total}, "
|
|
597
701
|
f"μ_final={mu:.2e}, |grad|∞={grad_inf:.2e}"
|
|
598
702
|
)
|
|
703
|
+
if has_eq:
|
|
704
|
+
detail += f", |c_eq|∞={eq_inf:.2e}"
|
|
599
705
|
if status == "kkt":
|
|
600
706
|
message = f"converged (kkt): {detail}"
|
|
601
707
|
elif status == "completed":
|
|
@@ -614,6 +720,8 @@ def solve_ipm(
|
|
|
614
720
|
message=message,
|
|
615
721
|
status=status,
|
|
616
722
|
solve_status=solve_status,
|
|
723
|
+
eq_inf_final=eq_inf,
|
|
724
|
+
step_limiters=limiters,
|
|
617
725
|
)
|
|
618
726
|
|
|
619
727
|
|