gridstate 2.4.1__tar.gz → 2.5.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.4.1 → gridstate-2.5.0}/CHANGELOG.md +17 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/PKG-INFO +1 -1
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/_version.py +3 -3
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/pipeline/config.py +23 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/pipeline/passes.py +1 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/pipeline/runner.py +9 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/preprocessing/ipm_setup.py +26 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/solution_hints.py +40 -1
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate.egg-info/PKG-INFO +1 -1
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate.egg-info/scm_version.json +2 -2
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_boxed_balance.py +27 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_numpy_only_runtime.py +2 -1
- gridstate-2.5.0/tests/test_solution_hints.py +98 -0
- gridstate-2.4.1/tests/test_solution_hints.py +0 -40
- {gridstate-2.4.1 → gridstate-2.5.0}/.github/workflows/ci.yml +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/.github/workflows/release.yml +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/.gitignore +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/.pre-commit-config.yaml +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/LICENSE +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/Makefile +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/README.md +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/build_cython.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/__init__.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/__pyinstaller/__init__.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/__pyinstaller/hook-gridstate.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/_arrays.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/adapters/__init__.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/adapters/from_pandapower.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/algebra/__init__.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/algebra/_selinv_kernels.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/algebra/base.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/algebra/estimators.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/algebra/jacobian_pattern.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/algebra/normalized_residuals.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/algorithms/__init__.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/algorithms/_gram_numba.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/algorithms/ipm.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/algorithms/ipm_pd.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/algorithms/kkt_solver.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/algorithms/status.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/algorithms/wls.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/api.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/bad_data_repass.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/bounds.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/constants.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/contract/__init__.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/contract/_legacy.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/contract/containers.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/contract/derived.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/contract/derived_tables.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/contract/serialize.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/contract/tables.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/contract/validate.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/contract/version.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/ipm_hints.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/load_characteristics.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/losses.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/measurement_ids.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/pipeline/__init__.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/pipeline/context.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/pipeline/manifest.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/pipeline/orchestration.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/pipeline/preparation.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/pipeline/steps.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/post_processing.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/preprocessing/__init__.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/preprocessing/_scan.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/preprocessing/meas_rows.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/preprocessing/mirror_voltage.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/preprocessing/node_props.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/preprocessing/pseudo_measurements.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/py.typed +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/quality_summary.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/resolve_pass.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/result.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/runtime.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/shunt_sanity.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/state.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/__init__.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/_filters.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/apply_resolved.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/generators.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/kinds.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/load_only_injection.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/loss_filter.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/measurements.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/noise.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/on_line.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/quality.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/rpn.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/shunts.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/sigma_floor.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/units.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/voltage_filter.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/telemetry/voltage_nominal.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/topology.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/units.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/utils.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/v_mirror.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/v_refine.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/validation/__init__.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/validation/_diagnostics.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/validation/bad_data.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/validation/chi2_test.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/validation/observability.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/working.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/ybus.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate/z_vector.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate.egg-info/SOURCES.txt +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate.egg-info/dependency_links.txt +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate.egg-info/entry_points.txt +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate.egg-info/requires.txt +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate.egg-info/scm_file_list.json +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/gridstate.egg-info/top_level.txt +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/pyproject.toml +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/setup.cfg +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/__init__.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/_golden.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/conftest.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_adapters_lazy_import.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_aggregate_generators.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_algebra_base.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_anti_overshoot.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_arrays_helpers.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_bad_data.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_bad_data_repass.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_bounds.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_breaker_reactance.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_chain_mirror.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_contract.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_contract_edge_cases.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_contract_runtime.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/golden/case118.json +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/golden/case118.npz +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/golden/case118_degraded.json +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/golden/case118_degraded.npz +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/golden/case14.json +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/golden/case14.npz +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/golden/case14_degraded.json +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/golden/case14_degraded.npz +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/golden/case30.json +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/golden/case30.npz +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/golden/case30_degraded.json +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/golden/case30_degraded.npz +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/golden/manifest.json +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/ieee/case118.npz +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/ieee/case118_truth.npz +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/ieee/case14.npz +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/ieee/case14_truth.npz +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/ieee/case30.npz +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_data/ieee/case30_truth.npz +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_derived_tables.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_enum_codes.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_flow_sigma_floor.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_from_pandapower.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_generator_shunt_kernels.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_golden.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_gram_numba.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_ieee_cases.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_ipm.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_ipm_balance_modes.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_ipm_hints.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_ipm_injection_roles.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_ipm_integration.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_ipm_pd.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_jacobian_pattern.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_kkt_solver.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_load_characteristic_segments.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_load_only_injection.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_losses_breakdown.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_losses_q_convention.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_losses_trafo_classification.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_materialize_kernel.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_measurement_filter_kernels.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_measurement_ids.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_mirror_voltage_kernel.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_near_critical_ranking.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_neutral_wording.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_noise_model.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_normalized_residuals.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_observability.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_on_line_kernel.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_perf_synthetic_network.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_pipeline.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_pipeline_config_validation.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_pipeline_idempotent.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_pipeline_invariant_errors.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_pipeline_step_order.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_pipeline_warm_start.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_pipeline_working_layer.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_post_processing.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_preparation_steps.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_prepare_network.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_pseudo_unknown_load.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_pseudo_v_unobservable.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_quality_summary_paths.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_reconcile_balance.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_refine_merge.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_resolve_pass.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_result_quality_summary.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_rn_selected_inversion.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_serialize_npz.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_shunt_sanity.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_solve_status.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_state.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_state_range.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_telemetry_apply_kernel.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_telemetry_bounds.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_telemetry_physics_kernels.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_tm_code_quality.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_topology_kernels.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_topology_port.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_transformer_from_shunt.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_units.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_units_array_kernels.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_v_mirror.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_v_refine.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_variance_charging.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_version.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_voltage_calibration_gen_nodes.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_voltage_range_filter_fallback.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_wls.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_wls_robustness.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_working_container.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_ybus.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tests/test_z_vector.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tools/gen_ieee_fixtures.py +0 -0
- {gridstate-2.4.1 → gridstate-2.5.0}/tools/golden.py +0 -0
|
@@ -9,6 +9,23 @@ 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.5.0 (2026-10-18)
|
|
13
|
+
|
|
14
|
+
### Behavior changes
|
|
15
|
+
|
|
16
|
+
- After the run, transit nodes (no `exist_gen` / `exist_load`) whose solved
|
|
17
|
+
injection exceeds `transit_injection_warn_mva` (new, default 5 MVA; 0 turns
|
|
18
|
+
it off) are listed in a WARNING: a data conflict, or a generation, load or
|
|
19
|
+
shunt the model does not describe. Estimates are not affected.
|
|
20
|
+
|
|
21
|
+
### Feat
|
|
22
|
+
|
|
23
|
+
- `ipm_transit_balance_weight_factor` (new, default 0 — off): the balance row
|
|
24
|
+
of a transit node gets the variance median(σ²_data) / factor (never looser
|
|
25
|
+
than the soft balance). Their injection is known to be zero; with the
|
|
26
|
+
default soft weight a data conflict spreads over them as a fictitious
|
|
27
|
+
injection.
|
|
28
|
+
|
|
12
29
|
## v2.4.1 (2026-10-18)
|
|
13
30
|
|
|
14
31
|
### 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.5.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 5, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g98bb957af'
|
|
@@ -396,6 +396,17 @@ class PipelineConfig:
|
|
|
396
396
|
"and logged with hints (e.g. the low-voltage root at a node whose V is barely "
|
|
397
397
|
"observable). 0 disables the check.",
|
|
398
398
|
)
|
|
399
|
+
transit_injection_warn_mva: float = _param(
|
|
400
|
+
5.0,
|
|
401
|
+
group=_G_EST,
|
|
402
|
+
label="Transit injection warning, MVA",
|
|
403
|
+
control="number",
|
|
404
|
+
min=0.0,
|
|
405
|
+
max=1e4,
|
|
406
|
+
help="After the run, transit nodes (no exist_gen/exist_load) whose solved "
|
|
407
|
+
"injection exceeds this are listed in a WARNING: a data conflict or an "
|
|
408
|
+
"undescribed generation, load or shunt. 0 disables the warning.",
|
|
409
|
+
)
|
|
399
410
|
kkt_solver: str = _param(
|
|
400
411
|
"auto",
|
|
401
412
|
group=_G_EST,
|
|
@@ -587,6 +598,18 @@ class PipelineConfig:
|
|
|
587
598
|
"balance row with variance median(σ²_data) / factor. The box carries the node's "
|
|
588
599
|
"freedom; a loose balance only hides data conflicts as an imbalance. 0 — off.",
|
|
589
600
|
)
|
|
601
|
+
ipm_transit_balance_weight_factor: float = _param(
|
|
602
|
+
0.0,
|
|
603
|
+
group=_G_IPM,
|
|
604
|
+
label="Balance weight of transit nodes",
|
|
605
|
+
control="number",
|
|
606
|
+
min=0.0,
|
|
607
|
+
max=1e6,
|
|
608
|
+
depends={"algorithm": "ipm"},
|
|
609
|
+
help="Transit nodes (no exist_gen/exist_load) get a balance row with variance "
|
|
610
|
+
"median(σ²_data) / factor. Their injection is known to be zero; a loose balance "
|
|
611
|
+
"turns data conflicts into a fictitious injection. 0 — off.",
|
|
612
|
+
)
|
|
590
613
|
ipm_prior_sigma2_bus_equiv_pu: float = _param(
|
|
591
614
|
0.01,
|
|
592
615
|
group=_G_IPM,
|
|
@@ -51,6 +51,7 @@ def _ipm_kwargs(cfg: PipelineConfig) -> dict:
|
|
|
51
51
|
"balance_weight_factor": cfg.ipm_balance_weight_factor,
|
|
52
52
|
"prior_sigma2_bus_equiv_pu": cfg.ipm_prior_sigma2_bus_equiv_pu,
|
|
53
53
|
"boxed_balance_weight_factor": cfg.ipm_boxed_balance_weight_factor,
|
|
54
|
+
"transit_balance_weight_factor": cfg.ipm_transit_balance_weight_factor,
|
|
54
55
|
"trust_region_scope": cfg.ipm_trust_region_scope,
|
|
55
56
|
"balance_mode": cfg.ipm_balance_mode,
|
|
56
57
|
"balance_tol_p_mw": cfg.ipm_balance_tol_p_mw,
|
|
@@ -64,6 +64,7 @@ neither :data:`STEPS` nor the manifest and have no ``step_start``):
|
|
|
64
64
|
|
|
65
65
|
from __future__ import annotations
|
|
66
66
|
|
|
67
|
+
import logging
|
|
67
68
|
import time
|
|
68
69
|
from collections.abc import Callable
|
|
69
70
|
from dataclasses import replace
|
|
@@ -76,10 +77,14 @@ from gridstate.pipeline.context import Step, _Ctx, _no_solution
|
|
|
76
77
|
from gridstate.pipeline.steps import STEPS
|
|
77
78
|
from gridstate.resolve_pass import fail_out_of_range, state_range_violation
|
|
78
79
|
from gridstate.result import SEResult
|
|
80
|
+
from gridstate.solution_hints import transit_injection_hints
|
|
79
81
|
from gridstate.state import InitialPoint
|
|
80
82
|
from gridstate.working import Working
|
|
81
83
|
|
|
82
84
|
|
|
85
|
+
logger = logging.getLogger(__name__)
|
|
86
|
+
|
|
87
|
+
|
|
83
88
|
# Consistency guard between config and derived (the config is used twice): the
|
|
84
89
|
# producer of the plans and the step executor must be gated by ONE config. An
|
|
85
90
|
# enabled step that did not get its plan means the configs diverged (derive
|
|
@@ -369,6 +374,10 @@ def run(
|
|
|
369
374
|
},
|
|
370
375
|
)
|
|
371
376
|
|
|
377
|
+
hints = transit_injection_hints(ctx.model.nodes.to_numpy(), cfg.transit_injection_warn_mva)
|
|
378
|
+
if hints:
|
|
379
|
+
logger.warning("%s", "\n".join(hints))
|
|
380
|
+
|
|
372
381
|
_emit(
|
|
373
382
|
on_event,
|
|
374
383
|
{
|
|
@@ -511,6 +511,15 @@ def _boxed_balance_masks(
|
|
|
511
511
|
return boxed[0], boxed[1]
|
|
512
512
|
|
|
513
513
|
|
|
514
|
+
def _transit_mask(nodes_arr: np.ndarray, node_ids: list[int]) -> np.ndarray:
|
|
515
|
+
"""Mask over ``node_ids``: transit nodes (neither ``exist_gen`` nor ``exist_load``)."""
|
|
516
|
+
rows = {int(r["id"]): r for r in nodes_arr}
|
|
517
|
+
return np.array(
|
|
518
|
+
[not (bool(rows[nid]["exist_gen"]) or bool(rows[nid]["exist_load"])) for nid in node_ids],
|
|
519
|
+
dtype=bool,
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
|
|
514
523
|
def _median_measurement_sigma2(sigma2: np.ndarray) -> float | None:
|
|
515
524
|
"""Median variance (p.u.²) of the data rows; ``None`` when there are none."""
|
|
516
525
|
positive = sigma2[sigma2 > 1e-15]
|
|
@@ -538,6 +547,7 @@ def build_ipm_setup(
|
|
|
538
547
|
balance_sigma2: float | None = None,
|
|
539
548
|
balance_weight_factor: float = 0.1,
|
|
540
549
|
boxed_balance_weight_factor: float = 0.0,
|
|
550
|
+
transit_balance_weight_factor: float = 0.0,
|
|
541
551
|
default_box_halfwidth_pu: float = 50.0,
|
|
542
552
|
prior_sigma2_normal_pu: float = 0.0,
|
|
543
553
|
prior_sigma2_bus_equiv_pu: float = 0.01,
|
|
@@ -576,6 +586,12 @@ def build_ipm_setup(
|
|
|
576
586
|
``median(σ²_data) / boxed_balance_weight_factor`` (never looser than
|
|
577
587
|
the soft one): the box already carries the node's freedom, so a
|
|
578
588
|
loose balance only hides data conflicts as an imbalance. ``0`` — off.
|
|
589
|
+
transit_balance_weight_factor: balance weight for transit nodes (no
|
|
590
|
+
``exist_gen`` / ``exist_load``): their injection is known to be
|
|
591
|
+
zero, so a loose balance turns data conflicts into a fictitious
|
|
592
|
+
injection. Their variance is ``median(σ²_data) /
|
|
593
|
+
transit_balance_weight_factor`` (never looser than the soft one).
|
|
594
|
+
``0`` — off.
|
|
579
595
|
default_box_halfwidth_pu: half-width (p.u.) of the default box
|
|
580
596
|
``[-hw, +hw]`` for an exist_* node with unset bounds.
|
|
581
597
|
Default 50 p.u. (±5 GW/GVAr), deliberately wider than any
|
|
@@ -790,6 +806,16 @@ def build_ipm_setup(
|
|
|
790
806
|
q_rows = sigma2_new[n_old + n_balance : n_old + 2 * n_balance]
|
|
791
807
|
p_rows[boxed_p] = np.minimum(p_rows[boxed_p], boxed_s2)
|
|
792
808
|
q_rows[boxed_q] = np.minimum(q_rows[boxed_q], boxed_s2)
|
|
809
|
+
if transit_balance_weight_factor > 0.0 and n_balance > 0 and not hard:
|
|
810
|
+
median_sigma2 = _median_measurement_sigma2(sigma2_old)
|
|
811
|
+
if median_sigma2 is not None:
|
|
812
|
+
transit_s2 = min(
|
|
813
|
+
median_sigma2 / float(transit_balance_weight_factor), balance_sigma2_eff
|
|
814
|
+
)
|
|
815
|
+
transit = _transit_mask(nodes_arr, active_ids)
|
|
816
|
+
for start in (n_old, n_old + n_balance):
|
|
817
|
+
rows = sigma2_new[start : start + n_balance]
|
|
818
|
+
rows[transit] = np.minimum(rows[transit], transit_s2)
|
|
793
819
|
if n_prior > 0:
|
|
794
820
|
prior_sigmas = np.concatenate(
|
|
795
821
|
[
|
|
@@ -6,7 +6,9 @@ wrong. A typical case is a node whose voltage magnitude is barely observable
|
|
|
6
6
|
problem then has a second, low-voltage root, and a change of the solver path
|
|
7
7
|
can land on it while the solver reports success. :func:`low_voltage_nodes`
|
|
8
8
|
finds such nodes; :func:`low_voltage_hints` and :func:`residual_hints` turn
|
|
9
|
-
the solved state into readable lines for the log.
|
|
9
|
+
the solved state into readable lines for the log. :func:`transit_injection_hints`
|
|
10
|
+
lists transit nodes left with an injection, which points at a data conflict or
|
|
11
|
+
at a node whose generation, load or shunt is not described in the model.
|
|
10
12
|
"""
|
|
11
13
|
|
|
12
14
|
from __future__ import annotations
|
|
@@ -162,10 +164,47 @@ def residual_hints(
|
|
|
162
164
|
return lines
|
|
163
165
|
|
|
164
166
|
|
|
167
|
+
def transit_injection_hints(nodes: np.ndarray, threshold_mva: float, *, top: int = 10) -> list[str]:
|
|
168
|
+
"""Transit nodes with a solved injection above ``threshold_mva``, largest first.
|
|
169
|
+
|
|
170
|
+
A transit node (active, neither ``exist_gen`` nor ``exist_load``) injects
|
|
171
|
+
nothing, so ``|p_inj_calc + j·q_inj_calc|`` there is what the estimate
|
|
172
|
+
could not fit elsewhere. ``nodes`` is a solved node table; empty when no
|
|
173
|
+
node exceeds the threshold or ``threshold_mva <= 0``.
|
|
174
|
+
"""
|
|
175
|
+
if threshold_mva <= 0.0 or not len(nodes):
|
|
176
|
+
return []
|
|
177
|
+
transit = (
|
|
178
|
+
nodes["status"].astype(bool)
|
|
179
|
+
& ~nodes["exist_gen"].astype(bool)
|
|
180
|
+
& ~nodes["exist_load"].astype(bool)
|
|
181
|
+
)
|
|
182
|
+
s_mva = np.hypot(nodes["p_inj_calc"], nodes["q_inj_calc"])
|
|
183
|
+
hit = np.flatnonzero(transit & np.isfinite(s_mva) & (s_mva > threshold_mva))
|
|
184
|
+
if not hit.size:
|
|
185
|
+
return []
|
|
186
|
+
order = hit[np.argsort(-s_mva[hit])]
|
|
187
|
+
lines = [
|
|
188
|
+
f"{hit.size} transit node(s) (no exist_gen/exist_load) inject more than "
|
|
189
|
+
f"{threshold_mva:g} MVA; a data conflict or an undescribed generation, load or "
|
|
190
|
+
"shunt at the node:"
|
|
191
|
+
]
|
|
192
|
+
for k in order[:top].tolist():
|
|
193
|
+
lines.append(
|
|
194
|
+
f" node {int(nodes['id'][k])} ({float(nodes['voltage_nominal'][k]):g} kV): "
|
|
195
|
+
f"P = {float(nodes['p_inj_calc'][k]):.1f} MW, "
|
|
196
|
+
f"Q = {float(nodes['q_inj_calc'][k]):.1f} MVAr"
|
|
197
|
+
)
|
|
198
|
+
if hit.size > top:
|
|
199
|
+
lines.append(f" ... and {hit.size - top} more")
|
|
200
|
+
return lines
|
|
201
|
+
|
|
202
|
+
|
|
165
203
|
__all__ = [
|
|
166
204
|
"KIND_NAMES",
|
|
167
205
|
"describe_row",
|
|
168
206
|
"low_voltage_hints",
|
|
169
207
|
"low_voltage_nodes",
|
|
170
208
|
"residual_hints",
|
|
209
|
+
"transit_injection_hints",
|
|
171
210
|
]
|
|
@@ -209,3 +209,30 @@ def test_compensator_active_power_is_not_a_box_part():
|
|
|
209
209
|
p_rows, q_rows = _setup(m, boxed_balance_weight_factor=10.0)
|
|
210
210
|
assert p_rows[2] < SOFT
|
|
211
211
|
assert q_rows[2] < SOFT
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def test_transit_weight_off_by_default():
|
|
215
|
+
assert PipelineConfig().ipm_transit_balance_weight_factor == 0.0
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_transit_nodes_get_the_weighted_row():
|
|
219
|
+
# Node order: 1 slack (generation box), 2 transit, 3 and 4 loads with a box.
|
|
220
|
+
p_rows, q_rows = _setup(_build_net_with_transit(), transit_balance_weight_factor=100.0)
|
|
221
|
+
tight = 2.5e-4 / 100.0
|
|
222
|
+
assert np.allclose(p_rows, [SOFT, tight, SOFT, SOFT])
|
|
223
|
+
assert np.allclose(q_rows, [SOFT, tight, SOFT, SOFT])
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def test_transit_row_never_looser_than_the_soft_row():
|
|
227
|
+
p_rows, q_rows = _setup(_build_net_with_transit(), transit_balance_weight_factor=0.01)
|
|
228
|
+
assert np.allclose(p_rows, SOFT)
|
|
229
|
+
assert np.allclose(q_rows, SOFT)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def test_transit_and_boxed_weights_combine():
|
|
233
|
+
p_rows, _ = _setup(
|
|
234
|
+
_build_net_with_transit(),
|
|
235
|
+
boxed_balance_weight_factor=10.0,
|
|
236
|
+
transit_balance_weight_factor=100.0,
|
|
237
|
+
)
|
|
238
|
+
assert np.allclose(p_rows, [2.5e-5, 2.5e-6, 2.5e-5, 2.5e-5])
|
|
@@ -152,7 +152,8 @@ _RUN_SRC = textwrap.dedent(
|
|
|
152
152
|
from gridstate.pipeline import run, PipelineConfig
|
|
153
153
|
|
|
154
154
|
working = build_working()
|
|
155
|
-
|
|
155
|
+
# The toy network leaves a transit injection; its WARNING would reach stderr.
|
|
156
|
+
cfg = PipelineConfig(materialize=False, transit_injection_warn_mva=0.0)
|
|
156
157
|
res = run(working, config=cfg)
|
|
157
158
|
vmax = float(np.max(res.v_pu)) if getattr(res, "v_pu", None) is not None and res.v_pu.size else float("nan")
|
|
158
159
|
print(f"RUN_OK success={bool(res.success)} iters={int(res.iterations)} vmax={vmax:.6f}")
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Plausibility check of the solved state (implausible V/Vnom) and its hints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from gridstate.api import estimate
|
|
11
|
+
from gridstate.pipeline import PipelineConfig, run
|
|
12
|
+
from gridstate.solution_hints import low_voltage_nodes, transit_injection_hints
|
|
13
|
+
from tests.test_boxed_balance import _build_net_with_transit
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_low_voltage_nodes():
|
|
17
|
+
v = np.array([1.0, 0.1, np.nan, 0.29, 0.31])
|
|
18
|
+
assert low_voltage_nodes(v, 0.3).tolist() == [1, 3]
|
|
19
|
+
assert low_voltage_nodes(v, 0.0).size == 0
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.parametrize("algorithm", ["wls", "ipm_pd"])
|
|
23
|
+
def test_implausible_state_is_not_a_success(caplog, algorithm):
|
|
24
|
+
# A threshold above every solved V flags all nodes: the run is marked failed
|
|
25
|
+
# and the warning names the nodes with a hint.
|
|
26
|
+
m = _build_net_with_transit()
|
|
27
|
+
with caplog.at_level(logging.WARNING, logger="gridstate.api"):
|
|
28
|
+
res = estimate(
|
|
29
|
+
m, algorithm=algorithm, include_quality_summary=False, implausible_v_ratio=2.0
|
|
30
|
+
)
|
|
31
|
+
assert not res.success
|
|
32
|
+
assert res.message.startswith("implausible solution: V/Vnom < 2")
|
|
33
|
+
text = "\n".join(r.getMessage() for r in caplog.records if "implausible" in r.getMessage())
|
|
34
|
+
assert "node " in text and "p.u.;" in text
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_check_disabled_keeps_the_solver_flag():
|
|
38
|
+
m = _build_net_with_transit()
|
|
39
|
+
res = estimate(m, algorithm="ipm_pd", include_quality_summary=False, implausible_v_ratio=0.0)
|
|
40
|
+
assert res.success
|
|
41
|
+
assert "implausible" not in res.message
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _nodes(p, q, exist_gen=0, exist_load=0, status=True):
|
|
45
|
+
nodes = np.zeros(
|
|
46
|
+
len(p),
|
|
47
|
+
dtype=[
|
|
48
|
+
("id", "i8"),
|
|
49
|
+
("voltage_nominal", "f8"),
|
|
50
|
+
("status", "?"),
|
|
51
|
+
("exist_gen", "i1"),
|
|
52
|
+
("exist_load", "i1"),
|
|
53
|
+
("p_inj_calc", "f8"),
|
|
54
|
+
("q_inj_calc", "f8"),
|
|
55
|
+
],
|
|
56
|
+
)
|
|
57
|
+
nodes["id"] = np.arange(1, len(p) + 1)
|
|
58
|
+
nodes["voltage_nominal"] = 220.0
|
|
59
|
+
nodes["status"] = status
|
|
60
|
+
nodes["exist_gen"] = exist_gen
|
|
61
|
+
nodes["exist_load"] = exist_load
|
|
62
|
+
nodes["p_inj_calc"] = p
|
|
63
|
+
nodes["q_inj_calc"] = q
|
|
64
|
+
return nodes
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_transit_injection_hints_list_largest_first():
|
|
68
|
+
nodes = _nodes([3.0, -0.5, 1.0, 0.0], [4.0, 0.2, -9.0, 0.0])
|
|
69
|
+
lines = transit_injection_hints(nodes, 4.0)
|
|
70
|
+
assert lines[0].startswith("2 transit node(s)")
|
|
71
|
+
assert lines[1].startswith(" node 3 (220 kV): P = 1.0 MW, Q = -9.0 MVAr")
|
|
72
|
+
assert lines[2].startswith(" node 1 ")
|
|
73
|
+
assert len(lines) == 3
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_transit_injection_hints_skip_marked_and_inactive_nodes():
|
|
77
|
+
assert transit_injection_hints(_nodes([50.0], [0.0], exist_gen=1), 5.0) == []
|
|
78
|
+
assert transit_injection_hints(_nodes([50.0], [0.0], exist_load=1), 5.0) == []
|
|
79
|
+
assert transit_injection_hints(_nodes([50.0], [0.0], status=False), 5.0) == []
|
|
80
|
+
assert transit_injection_hints(_nodes([50.0], [0.0]), 0.0) == []
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_transit_injection_hints_cap_the_list():
|
|
84
|
+
lines = transit_injection_hints(_nodes([10.0] * 4, [0.0] * 4), 5.0, top=2)
|
|
85
|
+
assert lines[-1] == " ... and 2 more"
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_pipeline_warns_about_transit_injection(caplog):
|
|
89
|
+
m = _build_net_with_transit()
|
|
90
|
+
cfg = PipelineConfig(algorithm="ipm_pd", transit_injection_warn_mva=1e-9)
|
|
91
|
+
with caplog.at_level(logging.WARNING, logger="gridstate.pipeline.runner"):
|
|
92
|
+
run(m, config=cfg)
|
|
93
|
+
text = "\n".join(r.getMessage() for r in caplog.records)
|
|
94
|
+
assert "transit node(s)" in text and "node 2 " in text
|
|
95
|
+
caplog.clear()
|
|
96
|
+
with caplog.at_level(logging.WARNING, logger="gridstate.pipeline.runner"):
|
|
97
|
+
run(m, config=PipelineConfig(algorithm="ipm_pd", transit_injection_warn_mva=0.0))
|
|
98
|
+
assert not any("transit node(s)" in r.getMessage() for r in caplog.records)
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"""Plausibility check of the solved state (implausible V/Vnom) and its hints."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import logging
|
|
6
|
-
|
|
7
|
-
import numpy as np
|
|
8
|
-
import pytest
|
|
9
|
-
|
|
10
|
-
from gridstate.api import estimate
|
|
11
|
-
from gridstate.solution_hints import low_voltage_nodes
|
|
12
|
-
from tests.test_boxed_balance import _build_net_with_transit
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def test_low_voltage_nodes():
|
|
16
|
-
v = np.array([1.0, 0.1, np.nan, 0.29, 0.31])
|
|
17
|
-
assert low_voltage_nodes(v, 0.3).tolist() == [1, 3]
|
|
18
|
-
assert low_voltage_nodes(v, 0.0).size == 0
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@pytest.mark.parametrize("algorithm", ["wls", "ipm_pd"])
|
|
22
|
-
def test_implausible_state_is_not_a_success(caplog, algorithm):
|
|
23
|
-
# A threshold above every solved V flags all nodes: the run is marked failed
|
|
24
|
-
# and the warning names the nodes with a hint.
|
|
25
|
-
m = _build_net_with_transit()
|
|
26
|
-
with caplog.at_level(logging.WARNING, logger="gridstate.api"):
|
|
27
|
-
res = estimate(
|
|
28
|
-
m, algorithm=algorithm, include_quality_summary=False, implausible_v_ratio=2.0
|
|
29
|
-
)
|
|
30
|
-
assert not res.success
|
|
31
|
-
assert res.message.startswith("implausible solution: V/Vnom < 2")
|
|
32
|
-
text = "\n".join(r.getMessage() for r in caplog.records if "implausible" in r.getMessage())
|
|
33
|
-
assert "node " in text and "p.u.;" in text
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def test_check_disabled_keeps_the_solver_flag():
|
|
37
|
-
m = _build_net_with_transit()
|
|
38
|
-
res = estimate(m, algorithm="ipm_pd", include_quality_summary=False, implausible_v_ratio=0.0)
|
|
39
|
-
assert res.success
|
|
40
|
-
assert "implausible" not in res.message
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|