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.
Files changed (224) hide show
  1. {gridstate-2.1.0 → gridstate-2.3.0}/CHANGELOG.md +63 -0
  2. {gridstate-2.1.0 → gridstate-2.3.0}/PKG-INFO +1 -1
  3. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/_version.py +3 -3
  4. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algorithms/ipm.py +109 -1
  5. gridstate-2.3.0/gridstate/algorithms/ipm_pd.py +617 -0
  6. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algorithms/kkt_solver.py +59 -18
  7. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/api.py +180 -41
  8. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/constants.py +11 -0
  9. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/derived.py +9 -1
  10. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/derived_tables.py +38 -0
  11. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/version.py +3 -1
  12. gridstate-2.3.0/gridstate/ipm_hints.py +282 -0
  13. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/config.py +94 -4
  14. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/passes.py +10 -2
  15. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/preparation.py +8 -0
  16. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/post_processing.py +19 -0
  17. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/ipm_setup.py +321 -8
  18. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/result.py +1 -1
  19. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/apply_resolved.py +137 -26
  20. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/z_vector.py +47 -0
  21. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/PKG-INFO +1 -1
  22. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/SOURCES.txt +7 -0
  23. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/scm_file_list.json +7 -0
  24. gridstate-2.3.0/gridstate.egg-info/scm_version.json +8 -0
  25. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_derived_tables.py +6 -0
  26. gridstate-2.3.0/tests/test_ipm_balance_modes.py +202 -0
  27. gridstate-2.3.0/tests/test_ipm_hints.py +113 -0
  28. gridstate-2.3.0/tests/test_ipm_injection_roles.py +275 -0
  29. gridstate-2.3.0/tests/test_ipm_pd.py +247 -0
  30. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline_config_validation.py +1 -1
  31. gridstate-2.3.0/tests/test_telemetry_bounds.py +86 -0
  32. gridstate-2.1.0/gridstate.egg-info/scm_version.json +0 -8
  33. {gridstate-2.1.0 → gridstate-2.3.0}/.github/workflows/ci.yml +0 -0
  34. {gridstate-2.1.0 → gridstate-2.3.0}/.github/workflows/release.yml +0 -0
  35. {gridstate-2.1.0 → gridstate-2.3.0}/.gitignore +0 -0
  36. {gridstate-2.1.0 → gridstate-2.3.0}/.pre-commit-config.yaml +0 -0
  37. {gridstate-2.1.0 → gridstate-2.3.0}/LICENSE +0 -0
  38. {gridstate-2.1.0 → gridstate-2.3.0}/Makefile +0 -0
  39. {gridstate-2.1.0 → gridstate-2.3.0}/README.md +0 -0
  40. {gridstate-2.1.0 → gridstate-2.3.0}/build_cython.py +0 -0
  41. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/__init__.py +0 -0
  42. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/__pyinstaller/__init__.py +0 -0
  43. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/__pyinstaller/hook-gridstate.py +0 -0
  44. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/_arrays.py +0 -0
  45. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/adapters/__init__.py +0 -0
  46. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/adapters/from_pandapower.py +0 -0
  47. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algebra/__init__.py +0 -0
  48. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algebra/_selinv_kernels.py +0 -0
  49. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algebra/base.py +0 -0
  50. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algebra/estimators.py +0 -0
  51. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algebra/jacobian_pattern.py +0 -0
  52. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algebra/normalized_residuals.py +0 -0
  53. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algorithms/__init__.py +0 -0
  54. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algorithms/_gram_numba.py +0 -0
  55. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algorithms/status.py +0 -0
  56. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/algorithms/wls.py +0 -0
  57. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/bad_data_repass.py +0 -0
  58. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/bounds.py +0 -0
  59. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/__init__.py +0 -0
  60. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/_legacy.py +0 -0
  61. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/containers.py +0 -0
  62. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/serialize.py +0 -0
  63. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/tables.py +0 -0
  64. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/contract/validate.py +0 -0
  65. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/losses.py +0 -0
  66. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/measurement_ids.py +0 -0
  67. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/__init__.py +0 -0
  68. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/context.py +0 -0
  69. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/manifest.py +0 -0
  70. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/orchestration.py +0 -0
  71. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/runner.py +0 -0
  72. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/pipeline/steps.py +0 -0
  73. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/__init__.py +0 -0
  74. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/_scan.py +0 -0
  75. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/meas_rows.py +0 -0
  76. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/mirror_voltage.py +0 -0
  77. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/node_props.py +0 -0
  78. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/preprocessing/pseudo_measurements.py +0 -0
  79. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/py.typed +0 -0
  80. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/quality_summary.py +0 -0
  81. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/resolve_pass.py +0 -0
  82. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/runtime.py +0 -0
  83. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/shunt_sanity.py +0 -0
  84. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/state.py +0 -0
  85. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/__init__.py +0 -0
  86. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/_filters.py +0 -0
  87. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/generators.py +0 -0
  88. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/kinds.py +0 -0
  89. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/load_only_injection.py +0 -0
  90. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/loss_filter.py +0 -0
  91. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/measurements.py +0 -0
  92. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/noise.py +0 -0
  93. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/on_line.py +0 -0
  94. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/quality.py +0 -0
  95. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/rpn.py +0 -0
  96. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/shunts.py +0 -0
  97. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/sigma_floor.py +0 -0
  98. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/units.py +0 -0
  99. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/voltage_filter.py +0 -0
  100. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/telemetry/voltage_nominal.py +0 -0
  101. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/topology.py +0 -0
  102. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/units.py +0 -0
  103. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/utils.py +0 -0
  104. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/v_mirror.py +0 -0
  105. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/v_refine.py +0 -0
  106. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/validation/__init__.py +0 -0
  107. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/validation/_diagnostics.py +0 -0
  108. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/validation/bad_data.py +0 -0
  109. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/validation/chi2_test.py +0 -0
  110. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/validation/observability.py +0 -0
  111. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/working.py +0 -0
  112. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate/ybus.py +0 -0
  113. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/dependency_links.txt +0 -0
  114. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/entry_points.txt +0 -0
  115. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/requires.txt +0 -0
  116. {gridstate-2.1.0 → gridstate-2.3.0}/gridstate.egg-info/top_level.txt +0 -0
  117. {gridstate-2.1.0 → gridstate-2.3.0}/pyproject.toml +0 -0
  118. {gridstate-2.1.0 → gridstate-2.3.0}/setup.cfg +0 -0
  119. {gridstate-2.1.0 → gridstate-2.3.0}/tests/__init__.py +0 -0
  120. {gridstate-2.1.0 → gridstate-2.3.0}/tests/_golden.py +0 -0
  121. {gridstate-2.1.0 → gridstate-2.3.0}/tests/conftest.py +0 -0
  122. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_adapters_lazy_import.py +0 -0
  123. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_aggregate_generators.py +0 -0
  124. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_algebra_base.py +0 -0
  125. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_anti_overshoot.py +0 -0
  126. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_arrays_helpers.py +0 -0
  127. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_bad_data.py +0 -0
  128. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_bad_data_repass.py +0 -0
  129. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_bounds.py +0 -0
  130. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_boxed_balance.py +0 -0
  131. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_breaker_reactance.py +0 -0
  132. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_chain_mirror.py +0 -0
  133. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_contract.py +0 -0
  134. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_contract_edge_cases.py +0 -0
  135. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_contract_runtime.py +0 -0
  136. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case118.json +0 -0
  137. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case118.npz +0 -0
  138. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case118_degraded.json +0 -0
  139. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case118_degraded.npz +0 -0
  140. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case14.json +0 -0
  141. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case14.npz +0 -0
  142. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case14_degraded.json +0 -0
  143. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case14_degraded.npz +0 -0
  144. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case30.json +0 -0
  145. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case30.npz +0 -0
  146. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case30_degraded.json +0 -0
  147. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/case30_degraded.npz +0 -0
  148. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/golden/manifest.json +0 -0
  149. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/ieee/case118.npz +0 -0
  150. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/ieee/case118_truth.npz +0 -0
  151. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/ieee/case14.npz +0 -0
  152. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/ieee/case14_truth.npz +0 -0
  153. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/ieee/case30.npz +0 -0
  154. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_data/ieee/case30_truth.npz +0 -0
  155. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_enum_codes.py +0 -0
  156. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_flow_sigma_floor.py +0 -0
  157. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_from_pandapower.py +0 -0
  158. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_generator_shunt_kernels.py +0 -0
  159. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_golden.py +0 -0
  160. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_gram_numba.py +0 -0
  161. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_ieee_cases.py +0 -0
  162. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_ipm.py +0 -0
  163. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_ipm_integration.py +0 -0
  164. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_jacobian_pattern.py +0 -0
  165. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_kkt_solver.py +0 -0
  166. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_load_only_injection.py +0 -0
  167. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_losses_breakdown.py +0 -0
  168. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_losses_q_convention.py +0 -0
  169. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_losses_trafo_classification.py +0 -0
  170. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_materialize_kernel.py +0 -0
  171. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_measurement_filter_kernels.py +0 -0
  172. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_measurement_ids.py +0 -0
  173. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_mirror_voltage_kernel.py +0 -0
  174. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_near_critical_ranking.py +0 -0
  175. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_neutral_wording.py +0 -0
  176. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_noise_model.py +0 -0
  177. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_normalized_residuals.py +0 -0
  178. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_numpy_only_runtime.py +0 -0
  179. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_observability.py +0 -0
  180. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_on_line_kernel.py +0 -0
  181. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_perf_synthetic_network.py +0 -0
  182. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline.py +0 -0
  183. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline_idempotent.py +0 -0
  184. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline_invariant_errors.py +0 -0
  185. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline_step_order.py +0 -0
  186. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline_warm_start.py +0 -0
  187. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pipeline_working_layer.py +0 -0
  188. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_post_processing.py +0 -0
  189. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_preparation_steps.py +0 -0
  190. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_prepare_network.py +0 -0
  191. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pseudo_unknown_load.py +0 -0
  192. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_pseudo_v_unobservable.py +0 -0
  193. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_quality_summary_paths.py +0 -0
  194. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_reconcile_balance.py +0 -0
  195. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_refine_merge.py +0 -0
  196. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_resolve_pass.py +0 -0
  197. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_result_quality_summary.py +0 -0
  198. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_rn_selected_inversion.py +0 -0
  199. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_serialize_npz.py +0 -0
  200. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_shunt_sanity.py +0 -0
  201. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_solve_status.py +0 -0
  202. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_state.py +0 -0
  203. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_state_range.py +0 -0
  204. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_telemetry_apply_kernel.py +0 -0
  205. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_telemetry_physics_kernels.py +0 -0
  206. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_tm_code_quality.py +0 -0
  207. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_topology_kernels.py +0 -0
  208. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_topology_port.py +0 -0
  209. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_transformer_from_shunt.py +0 -0
  210. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_units.py +0 -0
  211. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_units_array_kernels.py +0 -0
  212. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_v_mirror.py +0 -0
  213. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_v_refine.py +0 -0
  214. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_variance_charging.py +0 -0
  215. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_version.py +0 -0
  216. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_voltage_calibration_gen_nodes.py +0 -0
  217. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_voltage_range_filter_fallback.py +0 -0
  218. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_wls.py +0 -0
  219. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_wls_robustness.py +0 -0
  220. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_working_container.py +0 -0
  221. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_ybus.py +0 -0
  222. {gridstate-2.1.0 → gridstate-2.3.0}/tests/test_z_vector.py +0 -0
  223. {gridstate-2.1.0 → gridstate-2.3.0}/tools/gen_ieee_fixtures.py +0 -0
  224. {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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gridstate
3
- Version: 2.1.0
3
+ Version: 2.3.0
4
4
  Summary: Power system State Estimation: pure-Python WLS/IPM on numpy/scipy, input via a versioned file contract (npz).
5
5
  Author-email: Evgeny Istomin <gena@alpisto.eu>
6
6
  License: MIT
@@ -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.1.0'
22
- __version_tuple__ = version_tuple = (2, 1, 0)
21
+ __version__ = version = '2.3.0'
22
+ __version_tuple__ = version_tuple = (2, 3, 0)
23
23
 
24
- __commit_id__ = commit_id = 'g2560497db'
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