gridstate 2.5.0__tar.gz → 2.7.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 (227) hide show
  1. {gridstate-2.5.0 → gridstate-2.7.0}/CHANGELOG.md +36 -0
  2. {gridstate-2.5.0 → gridstate-2.7.0}/PKG-INFO +1 -1
  3. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/_version.py +3 -3
  4. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/algorithms/ipm_pd.py +13 -1
  5. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/api.py +5 -6
  6. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/ipm_hints.py +1 -1
  7. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/pipeline/config.py +32 -0
  8. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/pipeline/passes.py +3 -0
  9. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/preprocessing/ipm_setup.py +55 -10
  10. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate.egg-info/PKG-INFO +1 -1
  11. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate.egg-info/scm_version.json +2 -2
  12. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_ipm_balance_modes.py +67 -0
  13. {gridstate-2.5.0 → gridstate-2.7.0}/.github/workflows/ci.yml +0 -0
  14. {gridstate-2.5.0 → gridstate-2.7.0}/.github/workflows/release.yml +0 -0
  15. {gridstate-2.5.0 → gridstate-2.7.0}/.gitignore +0 -0
  16. {gridstate-2.5.0 → gridstate-2.7.0}/.pre-commit-config.yaml +0 -0
  17. {gridstate-2.5.0 → gridstate-2.7.0}/LICENSE +0 -0
  18. {gridstate-2.5.0 → gridstate-2.7.0}/Makefile +0 -0
  19. {gridstate-2.5.0 → gridstate-2.7.0}/README.md +0 -0
  20. {gridstate-2.5.0 → gridstate-2.7.0}/build_cython.py +0 -0
  21. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/__init__.py +0 -0
  22. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/__pyinstaller/__init__.py +0 -0
  23. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/__pyinstaller/hook-gridstate.py +0 -0
  24. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/_arrays.py +0 -0
  25. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/adapters/__init__.py +0 -0
  26. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/adapters/from_pandapower.py +0 -0
  27. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/algebra/__init__.py +0 -0
  28. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/algebra/_selinv_kernels.py +0 -0
  29. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/algebra/base.py +0 -0
  30. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/algebra/estimators.py +0 -0
  31. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/algebra/jacobian_pattern.py +0 -0
  32. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/algebra/normalized_residuals.py +0 -0
  33. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/algorithms/__init__.py +0 -0
  34. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/algorithms/_gram_numba.py +0 -0
  35. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/algorithms/ipm.py +0 -0
  36. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/algorithms/kkt_solver.py +0 -0
  37. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/algorithms/status.py +0 -0
  38. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/algorithms/wls.py +0 -0
  39. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/bad_data_repass.py +0 -0
  40. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/bounds.py +0 -0
  41. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/constants.py +0 -0
  42. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/contract/__init__.py +0 -0
  43. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/contract/_legacy.py +0 -0
  44. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/contract/containers.py +0 -0
  45. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/contract/derived.py +0 -0
  46. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/contract/derived_tables.py +0 -0
  47. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/contract/serialize.py +0 -0
  48. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/contract/tables.py +0 -0
  49. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/contract/validate.py +0 -0
  50. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/contract/version.py +0 -0
  51. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/load_characteristics.py +0 -0
  52. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/losses.py +0 -0
  53. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/measurement_ids.py +0 -0
  54. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/pipeline/__init__.py +0 -0
  55. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/pipeline/context.py +0 -0
  56. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/pipeline/manifest.py +0 -0
  57. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/pipeline/orchestration.py +0 -0
  58. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/pipeline/preparation.py +0 -0
  59. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/pipeline/runner.py +0 -0
  60. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/pipeline/steps.py +0 -0
  61. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/post_processing.py +0 -0
  62. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/preprocessing/__init__.py +0 -0
  63. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/preprocessing/_scan.py +0 -0
  64. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/preprocessing/meas_rows.py +0 -0
  65. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/preprocessing/mirror_voltage.py +0 -0
  66. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/preprocessing/node_props.py +0 -0
  67. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/preprocessing/pseudo_measurements.py +0 -0
  68. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/py.typed +0 -0
  69. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/quality_summary.py +0 -0
  70. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/resolve_pass.py +0 -0
  71. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/result.py +0 -0
  72. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/runtime.py +0 -0
  73. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/shunt_sanity.py +0 -0
  74. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/solution_hints.py +0 -0
  75. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/state.py +0 -0
  76. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/__init__.py +0 -0
  77. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/_filters.py +0 -0
  78. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/apply_resolved.py +0 -0
  79. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/generators.py +0 -0
  80. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/kinds.py +0 -0
  81. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/load_only_injection.py +0 -0
  82. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/loss_filter.py +0 -0
  83. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/measurements.py +0 -0
  84. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/noise.py +0 -0
  85. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/on_line.py +0 -0
  86. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/quality.py +0 -0
  87. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/rpn.py +0 -0
  88. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/shunts.py +0 -0
  89. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/sigma_floor.py +0 -0
  90. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/units.py +0 -0
  91. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/voltage_filter.py +0 -0
  92. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/telemetry/voltage_nominal.py +0 -0
  93. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/topology.py +0 -0
  94. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/units.py +0 -0
  95. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/utils.py +0 -0
  96. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/v_mirror.py +0 -0
  97. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/v_refine.py +0 -0
  98. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/validation/__init__.py +0 -0
  99. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/validation/_diagnostics.py +0 -0
  100. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/validation/bad_data.py +0 -0
  101. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/validation/chi2_test.py +0 -0
  102. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/validation/observability.py +0 -0
  103. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/working.py +0 -0
  104. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/ybus.py +0 -0
  105. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate/z_vector.py +0 -0
  106. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate.egg-info/SOURCES.txt +0 -0
  107. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate.egg-info/dependency_links.txt +0 -0
  108. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate.egg-info/entry_points.txt +0 -0
  109. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate.egg-info/requires.txt +0 -0
  110. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate.egg-info/scm_file_list.json +0 -0
  111. {gridstate-2.5.0 → gridstate-2.7.0}/gridstate.egg-info/top_level.txt +0 -0
  112. {gridstate-2.5.0 → gridstate-2.7.0}/pyproject.toml +0 -0
  113. {gridstate-2.5.0 → gridstate-2.7.0}/setup.cfg +0 -0
  114. {gridstate-2.5.0 → gridstate-2.7.0}/tests/__init__.py +0 -0
  115. {gridstate-2.5.0 → gridstate-2.7.0}/tests/_golden.py +0 -0
  116. {gridstate-2.5.0 → gridstate-2.7.0}/tests/conftest.py +0 -0
  117. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_adapters_lazy_import.py +0 -0
  118. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_aggregate_generators.py +0 -0
  119. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_algebra_base.py +0 -0
  120. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_anti_overshoot.py +0 -0
  121. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_arrays_helpers.py +0 -0
  122. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_bad_data.py +0 -0
  123. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_bad_data_repass.py +0 -0
  124. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_bounds.py +0 -0
  125. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_boxed_balance.py +0 -0
  126. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_breaker_reactance.py +0 -0
  127. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_chain_mirror.py +0 -0
  128. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_contract.py +0 -0
  129. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_contract_edge_cases.py +0 -0
  130. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_contract_runtime.py +0 -0
  131. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/golden/case118.json +0 -0
  132. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/golden/case118.npz +0 -0
  133. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/golden/case118_degraded.json +0 -0
  134. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/golden/case118_degraded.npz +0 -0
  135. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/golden/case14.json +0 -0
  136. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/golden/case14.npz +0 -0
  137. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/golden/case14_degraded.json +0 -0
  138. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/golden/case14_degraded.npz +0 -0
  139. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/golden/case30.json +0 -0
  140. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/golden/case30.npz +0 -0
  141. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/golden/case30_degraded.json +0 -0
  142. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/golden/case30_degraded.npz +0 -0
  143. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/golden/manifest.json +0 -0
  144. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/ieee/case118.npz +0 -0
  145. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/ieee/case118_truth.npz +0 -0
  146. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/ieee/case14.npz +0 -0
  147. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/ieee/case14_truth.npz +0 -0
  148. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/ieee/case30.npz +0 -0
  149. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_data/ieee/case30_truth.npz +0 -0
  150. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_derived_tables.py +0 -0
  151. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_enum_codes.py +0 -0
  152. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_flow_sigma_floor.py +0 -0
  153. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_from_pandapower.py +0 -0
  154. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_generator_shunt_kernels.py +0 -0
  155. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_golden.py +0 -0
  156. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_gram_numba.py +0 -0
  157. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_ieee_cases.py +0 -0
  158. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_ipm.py +0 -0
  159. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_ipm_hints.py +0 -0
  160. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_ipm_injection_roles.py +0 -0
  161. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_ipm_integration.py +0 -0
  162. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_ipm_pd.py +0 -0
  163. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_jacobian_pattern.py +0 -0
  164. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_kkt_solver.py +0 -0
  165. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_load_characteristic_segments.py +0 -0
  166. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_load_only_injection.py +0 -0
  167. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_losses_breakdown.py +0 -0
  168. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_losses_q_convention.py +0 -0
  169. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_losses_trafo_classification.py +0 -0
  170. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_materialize_kernel.py +0 -0
  171. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_measurement_filter_kernels.py +0 -0
  172. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_measurement_ids.py +0 -0
  173. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_mirror_voltage_kernel.py +0 -0
  174. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_near_critical_ranking.py +0 -0
  175. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_neutral_wording.py +0 -0
  176. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_noise_model.py +0 -0
  177. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_normalized_residuals.py +0 -0
  178. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_numpy_only_runtime.py +0 -0
  179. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_observability.py +0 -0
  180. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_on_line_kernel.py +0 -0
  181. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_perf_synthetic_network.py +0 -0
  182. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_pipeline.py +0 -0
  183. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_pipeline_config_validation.py +0 -0
  184. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_pipeline_idempotent.py +0 -0
  185. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_pipeline_invariant_errors.py +0 -0
  186. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_pipeline_step_order.py +0 -0
  187. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_pipeline_warm_start.py +0 -0
  188. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_pipeline_working_layer.py +0 -0
  189. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_post_processing.py +0 -0
  190. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_preparation_steps.py +0 -0
  191. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_prepare_network.py +0 -0
  192. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_pseudo_unknown_load.py +0 -0
  193. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_pseudo_v_unobservable.py +0 -0
  194. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_quality_summary_paths.py +0 -0
  195. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_reconcile_balance.py +0 -0
  196. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_refine_merge.py +0 -0
  197. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_resolve_pass.py +0 -0
  198. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_result_quality_summary.py +0 -0
  199. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_rn_selected_inversion.py +0 -0
  200. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_serialize_npz.py +0 -0
  201. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_shunt_sanity.py +0 -0
  202. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_solution_hints.py +0 -0
  203. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_solve_status.py +0 -0
  204. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_state.py +0 -0
  205. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_state_range.py +0 -0
  206. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_telemetry_apply_kernel.py +0 -0
  207. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_telemetry_bounds.py +0 -0
  208. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_telemetry_physics_kernels.py +0 -0
  209. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_tm_code_quality.py +0 -0
  210. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_topology_kernels.py +0 -0
  211. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_topology_port.py +0 -0
  212. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_transformer_from_shunt.py +0 -0
  213. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_units.py +0 -0
  214. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_units_array_kernels.py +0 -0
  215. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_v_mirror.py +0 -0
  216. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_v_refine.py +0 -0
  217. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_variance_charging.py +0 -0
  218. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_version.py +0 -0
  219. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_voltage_calibration_gen_nodes.py +0 -0
  220. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_voltage_range_filter_fallback.py +0 -0
  221. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_wls.py +0 -0
  222. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_wls_robustness.py +0 -0
  223. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_working_container.py +0 -0
  224. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_ybus.py +0 -0
  225. {gridstate-2.5.0 → gridstate-2.7.0}/tests/test_z_vector.py +0 -0
  226. {gridstate-2.5.0 → gridstate-2.7.0}/tools/gen_ieee_fixtures.py +0 -0
  227. {gridstate-2.5.0 → gridstate-2.7.0}/tools/golden.py +0 -0
@@ -9,6 +9,42 @@ 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.7.0 (2026-10-18)
13
+
14
+ ### Behavior changes
15
+
16
+ - `ipm_pd` stops the Huber reweighting once the complementarity μ is at or
17
+ below `huber_freeze_mu` (new solver argument, default 1e-4) and keeps the
18
+ last weights. Estimates move slightly (≤ 0.005 kV median |ΔV| on the test
19
+ models, individual nodes up to ~2e-3 p.u.); iteration counts drop.
20
+
21
+ ### Perf
22
+
23
+ - `ipm_pd` end game with Huber: rows whose residual sits at the threshold
24
+ flipped their weight every iteration, the merit search halved every other
25
+ step and the solve zigzagged at a linear rate. Equality rows (the hard
26
+ transit balance) made it markedly worse: on the test models the hard mode
27
+ needed up to 109 iterations per solve where the soft one needs 18. With the
28
+ weights frozen: 28; the hard mode now runs within ~10–15 % of the soft one.
29
+
30
+ ## v2.6.0 (2026-10-18)
31
+
32
+ ### Behavior changes
33
+
34
+ - `ipm_pd` gets the 300-iteration budget with equality rows too (it was 100
35
+ before the primal fallback), including the global `ipm_balance_mode="hard"`:
36
+ with the adaptive box damping and the measurement-space stopping test of
37
+ v2.4.1 the long budget converges where the short one fell back.
38
+
39
+ ### Feat
40
+
41
+ - `ipm_transit_balance_mode` (new, default `soft`): `hard` makes the P and Q
42
+ balance rows of transit nodes (no `exist_gen` / `exist_load`) equality
43
+ constraints of the solver, to `ipm_transit_balance_tol_p_mw` (new, default
44
+ 0.01) / `ipm_transit_balance_tol_q_mvar` (new, default 0.1); the other
45
+ balance rows stay soft. The global hard balance overrides it. `IPMSetup`
46
+ gains `eq_rows` / `eq_tol`.
47
+
12
48
  ## v2.5.0 (2026-10-18)
13
49
 
14
50
  ### Behavior changes
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gridstate
3
- Version: 2.5.0
3
+ Version: 2.7.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.5.0'
22
- __version_tuple__ = version_tuple = (2, 5, 0)
21
+ __version__ = version = '2.7.0'
22
+ __version_tuple__ = version_tuple = (2, 7, 0)
23
23
 
24
- __commit_id__ = commit_id = 'g98bb957af'
24
+ __commit_id__ = commit_id = 'g9810ca9e9'
@@ -244,6 +244,7 @@ def solve_ipm_pd(
244
244
  huber_use_mad: bool = False,
245
245
  huber_adaptive_k: float = 6.0,
246
246
  huber_warmup_iters: int = 5,
247
+ huber_freeze_mu: float = 1e-4,
247
248
  kkt_solver: KKTSolver | None = None,
248
249
  eq_prox: float = 1e-9,
249
250
  box_prox: float = 1e-6,
@@ -286,6 +287,15 @@ def solve_ipm_pd(
286
287
  huber_*: SHGM-IRLS reweighting of the rows of ``huber_mask``, as in
287
288
  ``solve_ipm``; the adaptive scale is recomputed every iteration
288
289
  (μ changes every iteration).
290
+ huber_freeze_mu: the reweighting stops once the complementarity μ is
291
+ at or below it and the last weights are kept. Rows whose residual
292
+ sits at the Huber threshold flip their weight from one iteration to
293
+ the next, so the objective the step is computed for changes every
294
+ iteration and the merit search halves every other step; with the
295
+ weights frozen the end game is a plain weighted least squares and
296
+ converges at the Newton rate. Equality rows push the data
297
+ conflicts into the residuals and make this markedly worse. ``0``
298
+ reweights to the end.
289
299
  kkt_solver: Newton-system solver (``None`` — scipy).
290
300
  eq_prox: with equality rows, a proximal term ``eq_prox·max(ρ)`` on the
291
301
  diagonal of the box coordinates. A hard balance fixes only the net
@@ -414,8 +424,9 @@ def solve_ipm_pd(
414
424
  status = "stalled"
415
425
  message = ""
416
426
  it = 0
427
+ mu_last = float("inf")
417
428
  for it in range(max_iter):
418
- if use_huber and it >= huber_warmup_iters:
429
+ if use_huber and it >= huber_warmup_iters and mu_last > huber_freeze_mu:
419
430
  reweighter.reset_adaptive()
420
431
  w = w_base * reweighter.weights(r)
421
432
  w_data = np.where(is_eq, 0.0, w)
@@ -423,6 +434,7 @@ def solve_ipm_pd(
423
434
  r_l = g[il] - lo[il] - s_l
424
435
  r_u = hi[iu] - g[iu] - s_u
425
436
  mu = (float(s_l @ y_l) + float(s_u @ y_u)) / n_pairs if n_pairs else 0.0
437
+ mu_last = mu
426
438
  eq_inf = float(np.max(np.abs(c))) if c.size else 0.0
427
439
  feas_inf = float(np.max(np.abs(np.concatenate([r_l, r_u])))) if n_pairs else 0.0
428
440
  if (
@@ -826,7 +826,7 @@ def _run_ipm(
826
826
  delta, v, _pg, _qg, _pn, _qn = unpack_full(x, layout_ipm)
827
827
  return algebra.evaluate_jacobian(v, delta)
828
828
 
829
- eq_idx = setup.balance_rows if setup.balance_tol is not None else None
829
+ eq_idx = setup.eq_rows
830
830
  pd_result: IPMResult | None = None
831
831
  if solver == "ipm_pd":
832
832
  mb_rows = mb_lo = mb_hi = None
@@ -852,13 +852,12 @@ def _run_ipm(
852
852
  meas_box_lo=mb_lo,
853
853
  meas_box_hi=mb_hi,
854
854
  eq_idx=eq_idx,
855
- eq_tol=setup.balance_tol,
855
+ eq_tol=setup.eq_tol,
856
856
  mu_init=1.0,
857
857
  step_tol=max(tolerance, 1e-4),
858
858
  # Weakly determined units need up to ~260 iterations with separate
859
- # injection parts; a hard balance does not converge in that budget
860
- # either way, so it keeps the short one before the fallback.
861
- max_iter=max(max_iterations, 100 if eq_idx is not None else 300),
859
+ # injection parts, with or without equality rows.
860
+ max_iter=max(max_iterations, 300),
862
861
  tr_radius=0.5,
863
862
  huber_c=huber_c,
864
863
  huber_mask=huber_mask,
@@ -910,7 +909,7 @@ def _run_ipm(
910
909
  huber_adaptive_k=2.0,
911
910
  kkt_solver=kkt,
912
911
  eq_idx=eq_idx,
913
- eq_tol=setup.balance_tol,
912
+ eq_tol=setup.eq_tol,
914
913
  )
915
914
  if solver == "ipm_pd":
916
915
  result.message = f"ipm_pd fallback to ipm ({pd_message}): {result.message}"
@@ -219,7 +219,7 @@ def ipm_convergence_hints(
219
219
  meas_index=setup.meas_index,
220
220
  network_pu=network_pu,
221
221
  # Equality rows: their σ is a penalty, not a measurement error.
222
- skip=setup.balance_rows if setup.balance_tol is not None else None,
222
+ skip=setup.eq_rows,
223
223
  part_rows=setup.role_rows,
224
224
  top=top,
225
225
  )
@@ -610,6 +610,38 @@ class PipelineConfig:
610
610
  "median(σ²_data) / factor. Their injection is known to be zero; a loose balance "
611
611
  "turns data conflicts into a fictitious injection. 0 — off.",
612
612
  )
613
+ ipm_transit_balance_mode: str = _param(
614
+ "soft",
615
+ group=_G_IPM,
616
+ label="Balance mode of transit nodes",
617
+ control="select",
618
+ choices=["soft", "hard"],
619
+ depends={"algorithm": "ipm"},
620
+ help="soft — the balance of a transit node (no exist_gen/exist_load) is a "
621
+ "weighted row like the others (ipm_transit_balance_weight_factor); hard — an "
622
+ "equality constraint to ipm_transit_balance_tol_p_mw / _q_mvar, the other "
623
+ "balance rows stay soft. No effect when ipm_balance_mode=hard.",
624
+ )
625
+ ipm_transit_balance_tol_p_mw: float = _param(
626
+ 0.01,
627
+ group=_G_IPM,
628
+ label="Transit balance tolerance P, MW",
629
+ control="number",
630
+ min=1e-6,
631
+ max=1e3,
632
+ depends={"ipm_transit_balance_mode": "hard"},
633
+ help="Admissible |P injection| of a transit node under the hard transit balance.",
634
+ )
635
+ ipm_transit_balance_tol_q_mvar: float = _param(
636
+ 0.1,
637
+ group=_G_IPM,
638
+ label="Transit balance tolerance Q, MVAr",
639
+ control="number",
640
+ min=1e-6,
641
+ max=1e3,
642
+ depends={"ipm_transit_balance_mode": "hard"},
643
+ help="Admissible |Q injection| of a transit node under the hard transit balance.",
644
+ )
613
645
  ipm_prior_sigma2_bus_equiv_pu: float = _param(
614
646
  0.01,
615
647
  group=_G_IPM,
@@ -52,6 +52,9 @@ def _ipm_kwargs(cfg: PipelineConfig) -> dict:
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
54
  "transit_balance_weight_factor": cfg.ipm_transit_balance_weight_factor,
55
+ "transit_balance_mode": cfg.ipm_transit_balance_mode,
56
+ "transit_balance_tol_p_mw": cfg.ipm_transit_balance_tol_p_mw,
57
+ "transit_balance_tol_q_mvar": cfg.ipm_transit_balance_tol_q_mvar,
55
58
  "trust_region_scope": cfg.ipm_trust_region_scope,
56
59
  "balance_mode": cfg.ipm_balance_mode,
57
60
  "balance_tol_p_mw": cfg.ipm_balance_tol_p_mw,
@@ -282,6 +282,11 @@ class IPMSetup:
282
282
  # ``balance_tol`` holds the admissible |imbalance| per balance row in p.u.
283
283
  # ``None`` in the soft mode.
284
284
  balance_tol: np.ndarray | None = None
285
+ # Rows the solver enforces as equalities and their admissible |c| (p.u.):
286
+ # every balance row in the hard mode, the transit balance rows with
287
+ # ``transit_balance_mode="hard"``; ``None`` — no equality rows.
288
+ eq_rows: np.ndarray | None = None
289
+ eq_tol: np.ndarray | None = None
285
290
  # ``injection_roles``: rows of ``z`` that measure a box variable directly
286
291
  # (a generation / load part of a node injection) and their measurement ids;
287
292
  # ``role_dropped_ids`` — part rows folded back into a net injection row.
@@ -556,6 +561,9 @@ def build_ipm_setup(
556
561
  hard_balance_rho_factor: float = 1e3,
557
562
  balance_tol_p_mw: float = 1.0,
558
563
  balance_tol_q_mvar: float = 10.0,
564
+ transit_balance_mode: str = "soft",
565
+ transit_balance_tol_p_mw: float = 0.01,
566
+ transit_balance_tol_q_mvar: float = 0.1,
559
567
  fixed_injection_from_input: bool = False,
560
568
  injection_roles: bool = False,
561
569
  measurements: _ArrayCollection | None = None,
@@ -628,6 +636,14 @@ def build_ipm_setup(
628
636
  hard_balance_rho_factor: see ``balance_mode``.
629
637
  balance_tol_p_mw, balance_tol_q_mvar: admissible |imbalance| of a hard
630
638
  P / Q balance row (MW / MVAr).
639
+ transit_balance_mode: ``"soft"`` (default) — the balance rows of transit
640
+ nodes (no ``exist_gen`` / ``exist_load``) are weighted like the
641
+ others (see ``transit_balance_weight_factor``); ``"hard"`` — they are
642
+ equality constraints with the penalty of ``balance_mode="hard"``,
643
+ the other balance rows stay soft. Ignored when ``balance_mode`` is
644
+ already ``"hard"``.
645
+ transit_balance_tol_p_mw, transit_balance_tol_q_mvar: admissible
646
+ |injection| of a transit node in the hard transit mode (MW / MVAr).
631
647
  fixed_injection_from_input: the balance row of a node carries the
632
648
  input injection of its parts without a box variable (no
633
649
  ``exist_gen`` / ``exist_load``) as a fixed term instead of zero.
@@ -732,6 +748,11 @@ def build_ipm_setup(
732
748
  if balance_mode not in ("soft", "hard"):
733
749
  raise ValueError(f"balance_mode={balance_mode!r} is not one of ['soft', 'hard']")
734
750
  hard = balance_mode == "hard"
751
+ if transit_balance_mode not in ("soft", "hard"):
752
+ raise ValueError(
753
+ f"transit_balance_mode={transit_balance_mode!r} is not one of ['soft', 'hard']"
754
+ )
755
+ transit_hard = transit_balance_mode == "hard" and not hard and n_balance > 0
735
756
 
736
757
  # ---- Prior measurements: z=0, individual σ². Only for box-vars with σ²>0 ----
737
758
  pgen_prior_arr = np.asarray(pgen_sec.prior_s2, dtype=np.float64)
@@ -816,6 +837,12 @@ def build_ipm_setup(
816
837
  for start in (n_old, n_old + n_balance):
817
838
  rows = sigma2_new[start : start + n_balance]
818
839
  rows[transit] = np.minimum(rows[transit], transit_s2)
840
+ transit_eq = np.zeros(0, dtype=np.int64)
841
+ if transit_hard:
842
+ transit = _transit_mask(nodes_arr, active_ids)
843
+ pos = np.flatnonzero(transit)
844
+ transit_eq = np.concatenate([n_old + pos, n_old + n_balance + pos]).astype(np.int64)
845
+ sigma2_new[transit_eq] = _adaptive_balance_sigma2(sigma2_old, hard_balance_rho_factor)
819
846
  if n_prior > 0:
820
847
  prior_sigmas = np.concatenate(
821
848
  [
@@ -893,6 +920,31 @@ def build_ipm_setup(
893
920
  meas_id=new_meas_id,
894
921
  )
895
922
 
923
+ balance_tol = (
924
+ np.concatenate(
925
+ [
926
+ np.full(n_balance, balance_tol_p_mw / BASE_MVA),
927
+ np.full(n_balance, balance_tol_q_mvar / BASE_MVA),
928
+ ]
929
+ )
930
+ if hard
931
+ else None
932
+ )
933
+ eq_rows: np.ndarray | None = None
934
+ eq_tol: np.ndarray | None = None
935
+ if hard:
936
+ eq_rows = np.arange(n_old, n_old + 2 * n_balance, dtype=np.int64)
937
+ eq_tol = balance_tol
938
+ elif transit_eq.size:
939
+ n_t = transit_eq.size // 2
940
+ eq_rows = transit_eq
941
+ eq_tol = np.concatenate(
942
+ [
943
+ np.full(n_t, transit_balance_tol_p_mw / BASE_MVA),
944
+ np.full(n_t, transit_balance_tol_q_mvar / BASE_MVA),
945
+ ]
946
+ )
947
+
896
948
  return IPMSetup(
897
949
  layout=layout,
898
950
  box_idx_in_state=box_idx_arr,
@@ -907,16 +959,9 @@ def build_ipm_setup(
907
959
  meas_index=meas_index_aug,
908
960
  balance_start=n_old,
909
961
  n_balance=n_balance,
910
- balance_tol=(
911
- np.concatenate(
912
- [
913
- np.full(n_balance, balance_tol_p_mw / BASE_MVA),
914
- np.full(n_balance, balance_tol_q_mvar / BASE_MVA),
915
- ]
916
- )
917
- if hard
918
- else None
919
- ),
962
+ balance_tol=balance_tol,
963
+ eq_rows=eq_rows,
964
+ eq_tol=eq_tol,
920
965
  role_rows=np.arange(role_start, n_new, dtype=np.int64),
921
966
  role_meas_ids=role_ids.astype(np.int64),
922
967
  role_dropped_ids=np.asarray(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gridstate
3
- Version: 2.5.0
3
+ Version: 2.7.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.5.0",
2
+ "tag": "2.7.0",
3
3
  "distance": 0,
4
- "node": "g98bb957af8b143153db7f269753619ec87bf6f6e",
4
+ "node": "g9810ca9e90847ed818258672eb84ba159fa60ea2",
5
5
  "dirty": false,
6
6
  "branch": "HEAD",
7
7
  "node_date": "2026-10-18"
@@ -75,6 +75,52 @@ def test_unknown_balance_mode_is_rejected():
75
75
  _setup(_build_net_with_transit(), balance_mode="strict")
76
76
 
77
77
 
78
+ def test_transit_hard_mode_makes_only_transit_rows_equalities():
79
+ # Node order 1..4: node 2 is the transit node.
80
+ setup = _setup(
81
+ _build_net_with_transit(),
82
+ transit_balance_mode="hard",
83
+ transit_balance_tol_p_mw=0.02,
84
+ transit_balance_tol_q_mvar=0.2,
85
+ hard_balance_rho_factor=1e3,
86
+ )
87
+ assert setup.balance_tol is None
88
+ assert setup.eq_rows is not None and setup.eq_tol is not None
89
+ np.testing.assert_array_equal(setup.eq_rows, [2 + 1, 2 + N_BALANCE + 1])
90
+ np.testing.assert_allclose(setup.eq_tol, [0.02 / BASE_MVA, 0.2 / BASE_MVA])
91
+ sigma2 = setup.r_matrix.diagonal()[2 : 2 + 2 * N_BALANCE]
92
+ soft = _setup(_build_net_with_transit()).r_matrix.diagonal()[2 : 2 + 2 * N_BALANCE]
93
+ is_eq = np.zeros(2 * N_BALANCE, dtype=bool)
94
+ is_eq[[1, N_BALANCE + 1]] = True
95
+ assert np.allclose(sigma2[is_eq], 2.5e-4 / 1e3)
96
+ np.testing.assert_array_equal(sigma2[~is_eq], soft[~is_eq])
97
+
98
+
99
+ def test_soft_modes_have_no_equality_rows():
100
+ setup = _setup(_build_net_with_transit(), transit_balance_weight_factor=10.0)
101
+ assert setup.eq_rows is None and setup.eq_tol is None
102
+
103
+
104
+ def test_global_hard_mode_overrides_the_transit_mode():
105
+ setup = _setup(
106
+ _build_net_with_transit(),
107
+ balance_mode="hard",
108
+ balance_tol_p_mw=2.0,
109
+ transit_balance_mode="hard",
110
+ transit_balance_tol_p_mw=0.02,
111
+ )
112
+ assert setup.eq_rows is not None and setup.eq_tol is not None
113
+ np.testing.assert_array_equal(setup.eq_rows, setup.balance_rows)
114
+ np.testing.assert_array_equal(setup.eq_tol, setup.balance_tol)
115
+
116
+
117
+ def test_unknown_transit_balance_mode_is_rejected():
118
+ with pytest.raises(ValueError, match="transit_balance_mode"):
119
+ _setup(_build_net_with_transit(), transit_balance_mode="strict")
120
+ with pytest.raises(ValueError, match="ipm_transit_balance_mode"):
121
+ PipelineConfig(algorithm="ipm_pd", ipm_transit_balance_mode="strict")
122
+
123
+
78
124
  def test_fixed_injection_from_input():
79
125
  m = _build_net_with_transit()
80
126
  # Transit node 2 gets a non-estimated load and generation.
@@ -131,6 +177,26 @@ def test_hard_balance_closes_the_estimated_imbalance(algorithm: str):
131
177
  assert imb_p <= 0.1 and imb_q <= 0.1
132
178
 
133
179
 
180
+ @pytest.mark.parametrize("algorithm", ["ipm", "ipm_pd"])
181
+ def test_transit_hard_balance_zeroes_the_transit_injection(algorithm: str):
182
+ # Soft, the transit node 2 absorbs the data conflict (≈ 4 MW / 14 MVAr).
183
+ soft = _build_net_with_transit()
184
+ estimate(soft, algorithm=algorithm, reconcile_balance=False, include_quality_summary=False)
185
+ n_soft = soft.nodes.to_numpy()
186
+ assert abs(n_soft["p_inj_calc"][1]) > 1.0 and abs(n_soft["q_inj_calc"][1]) > 1.0
187
+ m = _build_net_with_transit()
188
+ res = estimate(
189
+ m,
190
+ algorithm=algorithm,
191
+ reconcile_balance=False,
192
+ include_quality_summary=False,
193
+ transit_balance_mode="hard",
194
+ )
195
+ assert res.success
196
+ n = m.nodes.to_numpy()
197
+ assert abs(n["p_inj_calc"][1]) <= 0.01 and abs(n["q_inj_calc"][1]) <= 0.1
198
+
199
+
134
200
  def test_ipm_pd_close_to_ipm():
135
201
  results = {}
136
202
  for algorithm in ("ipm", "ipm_pd"):
@@ -170,6 +236,7 @@ def test_measurement_bounds_require_ipm_pd():
170
236
  def test_config_defaults_and_ranges():
171
237
  cfg = PipelineConfig(algorithm="ipm_pd")
172
238
  assert cfg.ipm_balance_mode == "soft"
239
+ assert cfg.ipm_transit_balance_mode == "soft"
173
240
  assert not cfg.ipm_measurement_bounds and not cfg.ipm_fixed_injection_from_input
174
241
  # The soft balance weight may now exceed the median measurement weight tenfold.
175
242
  dataclasses.replace(cfg, ipm_balance_weight_factor=1e3)
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