gridstate 2.7.0__tar.gz → 2.8.0__tar.gz

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