solverforge 0.5.0__tar.gz → 0.6.1__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 (267) hide show
  1. {solverforge-0.5.0 → solverforge-0.6.1}/Cargo.lock +13 -13
  2. {solverforge-0.5.0 → solverforge-0.6.1}/Cargo.toml +19 -8
  3. solverforge-0.6.1/PKG-INFO +409 -0
  4. solverforge-0.6.1/README.md +377 -0
  5. {solverforge-0.5.0 → solverforge-0.6.1}/pyproject.toml +1 -1
  6. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/__init__.py +26 -2
  7. solverforge-0.6.1/python/solverforge/_native.pyi +72 -0
  8. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/config.py +27 -3
  9. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/constraints.py +50 -18
  10. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/decorators.py +102 -14
  11. solverforge-0.6.1/python/solverforge/fields.py +494 -0
  12. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/groups.py +70 -2
  13. solverforge-0.6.1/python/solverforge/joiner.py +39 -0
  14. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/manager.py +31 -5
  15. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/model.py +167 -6
  16. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/solver.py +6 -4
  17. {solverforge-0.5.0 → solverforge-0.6.1}/src/bindings.rs +6 -0
  18. solverforge-0.6.1/src/config.rs +113 -0
  19. solverforge-0.6.1/src/constraints/compiled.rs +322 -0
  20. {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/list_precedence.rs +222 -54
  21. {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/mod.rs +1 -0
  22. {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/state.rs +411 -105
  23. solverforge-0.6.1/src/error.rs +44 -0
  24. solverforge-0.6.1/src/manager/candidate_trace.rs +388 -0
  25. solverforge-0.6.1/src/manager/events.rs +564 -0
  26. {solverforge-0.5.0 → solverforge-0.6.1}/src/manager/jobs.rs +102 -27
  27. solverforge-0.6.1/src/manager/mod.rs +7 -0
  28. solverforge-0.6.1/src/manager/provenance.rs +205 -0
  29. solverforge-0.6.1/src/runtime/candidate_metric.rs +119 -0
  30. solverforge-0.6.1/src/runtime/dynamic_assignment_group.rs +471 -0
  31. solverforge-0.6.1/src/runtime/dynamic_provider.rs +291 -0
  32. solverforge-0.6.1/src/runtime/list_slots.rs +798 -0
  33. solverforge-0.6.1/src/runtime/mod.rs +46 -0
  34. solverforge-0.6.1/src/runtime/scalar_slots.rs +337 -0
  35. solverforge-0.6.1/src/schema/compiled.rs +43 -0
  36. {solverforge-0.5.0 → solverforge-0.6.1}/src/schema/mod.rs +2 -0
  37. solverforge-0.6.1/src/schema/parse.rs +760 -0
  38. solverforge-0.6.1/src/schema/runtime_plan.rs +71 -0
  39. solverforge-0.6.1/src/schema/types.rs +266 -0
  40. solverforge-0.6.1/src/schema/validate.rs +118 -0
  41. {solverforge-0.5.0 → solverforge-0.6.1}/src/solver/api.rs +29 -51
  42. {solverforge-0.5.0 → solverforge-0.6.1}/src/solver/console.rs +14 -15
  43. solverforge-0.6.1/src/solver/solvable.rs +53 -0
  44. solverforge-0.6.1/src/state/callback_view.rs +616 -0
  45. solverforge-0.6.1/src/state/entity_table.rs +143 -0
  46. solverforge-0.6.1/src/state/marshal.rs +1195 -0
  47. solverforge-0.6.1/src/state/solution.rs +844 -0
  48. solverforge-0.6.1/src/state/variables.rs +18 -0
  49. solverforge-0.5.0/.github/workflows/ci.yml +0 -69
  50. solverforge-0.5.0/.github/workflows/release.yml +0 -200
  51. solverforge-0.5.0/.gitignore +0 -11
  52. solverforge-0.5.0/.pre-commit-config.yaml +0 -31
  53. solverforge-0.5.0/.python-version +0 -1
  54. solverforge-0.5.0/AGENTS.md +0 -121
  55. solverforge-0.5.0/CHANGELOG.md +0 -53
  56. solverforge-0.5.0/Makefile +0 -408
  57. solverforge-0.5.0/PKG-INFO +0 -336
  58. solverforge-0.5.0/README.md +0 -304
  59. solverforge-0.5.0/WIREFRAME.md +0 -306
  60. solverforge-0.5.0/examples/list_tsp.py +0 -20
  61. solverforge-0.5.0/examples/mixed_job_shop.py +0 -30
  62. solverforge-0.5.0/examples/nqueens.py +0 -36
  63. solverforge-0.5.0/examples/shift_scheduling.py +0 -45
  64. solverforge-0.5.0/examples/solverforge_deliveries/README.md +0 -85
  65. solverforge-0.5.0/examples/solverforge_deliveries/__init__.py +0 -23
  66. solverforge-0.5.0/examples/solverforge_deliveries/__main__.py +0 -32
  67. solverforge-0.5.0/examples/solverforge_deliveries/solver.toml +0 -51
  68. solverforge-0.5.0/examples/solverforge_deliveries/src/__init__.py +0 -1
  69. solverforge-0.5.0/examples/solverforge_deliveries/src/api/__init__.py +0 -1
  70. solverforge-0.5.0/examples/solverforge_deliveries/src/api/dto.py +0 -215
  71. solverforge-0.5.0/examples/solverforge_deliveries/src/api/mod.py +0 -3
  72. solverforge-0.5.0/examples/solverforge_deliveries/src/api/routes.py +0 -239
  73. solverforge-0.5.0/examples/solverforge_deliveries/src/api/sse.py +0 -42
  74. solverforge-0.5.0/examples/solverforge_deliveries/src/constraints/__init__.py +0 -5
  75. solverforge-0.5.0/examples/solverforge_deliveries/src/constraints/mod.py +0 -47
  76. solverforge-0.5.0/examples/solverforge_deliveries/src/data/__init__.py +0 -5
  77. solverforge-0.5.0/examples/solverforge_deliveries/src/data/data_seed/__init__.py +0 -5
  78. solverforge-0.5.0/examples/solverforge_deliveries/src/data/data_seed/entrypoints.py +0 -213
  79. solverforge-0.5.0/examples/solverforge_deliveries/src/data/data_seed/locations.json +0 -1472
  80. solverforge-0.5.0/examples/solverforge_deliveries/src/data/mod.py +0 -1
  81. solverforge-0.5.0/examples/solverforge_deliveries/src/domain/__init__.py +0 -33
  82. solverforge-0.5.0/examples/solverforge_deliveries/src/domain/delivery.py +0 -32
  83. solverforge-0.5.0/examples/solverforge_deliveries/src/domain/metrics.py +0 -404
  84. solverforge-0.5.0/examples/solverforge_deliveries/src/domain/mod.py +0 -1
  85. solverforge-0.5.0/examples/solverforge_deliveries/src/domain/plan.py +0 -84
  86. solverforge-0.5.0/examples/solverforge_deliveries/src/domain/vehicle.py +0 -41
  87. solverforge-0.5.0/examples/solverforge_deliveries/src/lib.py +0 -34
  88. solverforge-0.5.0/examples/solverforge_deliveries/src/main.py +0 -22
  89. solverforge-0.5.0/examples/solverforge_deliveries/src/solver/__init__.py +0 -5
  90. solverforge-0.5.0/examples/solverforge_deliveries/src/solver/mod.py +0 -1
  91. solverforge-0.5.0/examples/solverforge_deliveries/src/solver/service/__init__.py +0 -196
  92. solverforge-0.5.0/examples/solverforge_deliveries/src/solver/service/mod.py +0 -7
  93. solverforge-0.5.0/examples/solverforge_deliveries/src/solver/service/payload.py +0 -86
  94. solverforge-0.5.0/examples/solverforge_deliveries/static/app/main.mjs +0 -287
  95. solverforge-0.5.0/examples/solverforge_deliveries/static/app/models/core.mjs +0 -57
  96. solverforge-0.5.0/examples/solverforge_deliveries/static/app/models/formatters.mjs +0 -53
  97. solverforge-0.5.0/examples/solverforge_deliveries/static/app/models/preview.mjs +0 -154
  98. solverforge-0.5.0/examples/solverforge_deliveries/static/app/models/timeline.mjs +0 -94
  99. solverforge-0.5.0/examples/solverforge_deliveries/static/app/models.mjs +0 -8
  100. solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/api-guide.mjs +0 -29
  101. solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/components.mjs +0 -44
  102. solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/data-tables.mjs +0 -98
  103. solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/layout.mjs +0 -123
  104. solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/lifecycle.mjs +0 -23
  105. solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/modals.mjs +0 -63
  106. solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/overview.mjs +0 -172
  107. solverforge-0.5.0/examples/solverforge_deliveries/static/app.css +0 -196
  108. solverforge-0.5.0/examples/solverforge_deliveries/static/app.js +0 -3
  109. solverforge-0.5.0/examples/solverforge_deliveries/static/generated/ui-model.json +0 -25
  110. solverforge-0.5.0/examples/solverforge_deliveries/static/index.html +0 -22
  111. solverforge-0.5.0/examples/solverforge_deliveries/static/sf-config.json +0 -4
  112. solverforge-0.5.0/examples/solverforge_hospital/README.md +0 -81
  113. solverforge-0.5.0/examples/solverforge_hospital/__init__.py +0 -43
  114. solverforge-0.5.0/examples/solverforge_hospital/__main__.py +0 -32
  115. solverforge-0.5.0/examples/solverforge_hospital/solver.toml +0 -38
  116. solverforge-0.5.0/examples/solverforge_hospital/src/__init__.py +0 -41
  117. solverforge-0.5.0/examples/solverforge_hospital/src/api/__init__.py +0 -20
  118. solverforge-0.5.0/examples/solverforge_hospital/src/api/dto.py +0 -281
  119. solverforge-0.5.0/examples/solverforge_hospital/src/api/mod.py +0 -20
  120. solverforge-0.5.0/examples/solverforge_hospital/src/api/routes.py +0 -193
  121. solverforge-0.5.0/examples/solverforge_hospital/src/api/sse.py +0 -42
  122. solverforge-0.5.0/examples/solverforge_hospital/src/constraints/__init__.py +0 -3
  123. solverforge-0.5.0/examples/solverforge_hospital/src/constraints/assigned_shift.py +0 -14
  124. solverforge-0.5.0/examples/solverforge_hospital/src/constraints/balance_assignments.py +0 -20
  125. solverforge-0.5.0/examples/solverforge_hospital/src/constraints/desired_day.py +0 -24
  126. solverforge-0.5.0/examples/solverforge_hospital/src/constraints/minimum_rest.py +0 -33
  127. solverforge-0.5.0/examples/solverforge_hospital/src/constraints/mod.py +0 -42
  128. solverforge-0.5.0/examples/solverforge_hospital/src/constraints/one_shift_per_day.py +0 -16
  129. solverforge-0.5.0/examples/solverforge_hospital/src/constraints/overlapping_shift.py +0 -28
  130. solverforge-0.5.0/examples/solverforge_hospital/src/constraints/required_skill.py +0 -20
  131. solverforge-0.5.0/examples/solverforge_hospital/src/constraints/unavailable_employee.py +0 -32
  132. solverforge-0.5.0/examples/solverforge_hospital/src/constraints/undesired_day.py +0 -24
  133. solverforge-0.5.0/examples/solverforge_hospital/src/data/__init__.py +0 -23
  134. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/LARGE.json +0 -1
  135. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/__init__.py +0 -13
  136. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/availability.py +0 -16
  137. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/cohorts.py +0 -14
  138. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/coverage.py +0 -14
  139. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/demand.py +0 -9
  140. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/employees.py +0 -18
  141. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/entrypoints.py +0 -32
  142. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/large.py +0 -16
  143. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/preferences.py +0 -17
  144. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/shifts.py +0 -20
  145. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/skills.py +0 -15
  146. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/time_utils.py +0 -5
  147. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/validation.py +0 -12
  148. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/vocabulary.py +0 -10
  149. solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/witness.py +0 -10
  150. solverforge-0.5.0/examples/solverforge_hospital/src/data/mod.py +0 -3
  151. solverforge-0.5.0/examples/solverforge_hospital/src/domain/__init__.py +0 -59
  152. solverforge-0.5.0/examples/solverforge_hospital/src/domain/care_hub.py +0 -63
  153. solverforge-0.5.0/examples/solverforge_hospital/src/domain/employee.py +0 -32
  154. solverforge-0.5.0/examples/solverforge_hospital/src/domain/mod.py +0 -19
  155. solverforge-0.5.0/examples/solverforge_hospital/src/domain/plan.py +0 -256
  156. solverforge-0.5.0/examples/solverforge_hospital/src/lib.py +0 -56
  157. solverforge-0.5.0/examples/solverforge_hospital/src/main.py +0 -22
  158. solverforge-0.5.0/examples/solverforge_hospital/src/solver/__init__.py +0 -8
  159. solverforge-0.5.0/examples/solverforge_hospital/src/solver/mod.py +0 -8
  160. solverforge-0.5.0/examples/solverforge_hospital/src/solver/service/__init__.py +0 -202
  161. solverforge-0.5.0/examples/solverforge_hospital/src/solver/service/mod.py +0 -8
  162. solverforge-0.5.0/examples/solverforge_hospital/src/solver/service/payload.py +0 -86
  163. solverforge-0.5.0/examples/solverforge_hospital/static/app/main.mjs +0 -130
  164. solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/analysis-modal.mjs +0 -79
  165. solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/datetime.mjs +0 -60
  166. solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/employee-view.mjs +0 -95
  167. solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/grouping.mjs +0 -77
  168. solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/identity.mjs +0 -33
  169. solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/location-view.mjs +0 -42
  170. solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/presentation.mjs +0 -116
  171. solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/rail-renderer.mjs +0 -154
  172. solverforge-0.5.0/examples/solverforge_hospital/static/app/shell/api-guide.mjs +0 -104
  173. solverforge-0.5.0/examples/solverforge_hospital/static/app/shell/app-shell.mjs +0 -103
  174. solverforge-0.5.0/examples/solverforge_hospital/static/app/shell/app-state.mjs +0 -7
  175. solverforge-0.5.0/examples/solverforge_hospital/static/app/shell/config-loader.mjs +0 -63
  176. solverforge-0.5.0/examples/solverforge_hospital/static/app/shell/data-panel.mjs +0 -22
  177. solverforge-0.5.0/examples/solverforge_hospital/static/app/shell/solver-controller.mjs +0 -134
  178. solverforge-0.5.0/examples/solverforge_hospital/static/app/views/registry.mjs +0 -10
  179. solverforge-0.5.0/examples/solverforge_hospital/static/generated/ui-model.json +0 -41
  180. solverforge-0.5.0/examples/solverforge_hospital/static/index.html +0 -23
  181. solverforge-0.5.0/examples/solverforge_hospital/static/sf-config.json +0 -5
  182. solverforge-0.5.0/examples/vrp_owner_hooks.py +0 -21
  183. solverforge-0.5.0/python/solverforge/_native.pyi +0 -30
  184. solverforge-0.5.0/python/solverforge/fields.py +0 -133
  185. solverforge-0.5.0/python/solverforge/joiner.py +0 -28
  186. solverforge-0.5.0/scripts/verify_release_artifacts.py +0 -211
  187. solverforge-0.5.0/scripts/verify_solverforge_release_base.py +0 -109
  188. solverforge-0.5.0/src/config.rs +0 -58
  189. solverforge-0.5.0/src/error.rs +0 -9
  190. solverforge-0.5.0/src/manager/events.rs +0 -154
  191. solverforge-0.5.0/src/manager/mod.rs +0 -4
  192. solverforge-0.5.0/src/runtime/distance.rs +0 -17
  193. solverforge-0.5.0/src/runtime/dynamic_scalar_search.rs +0 -6307
  194. solverforge-0.5.0/src/runtime/list_slots.rs +0 -28
  195. solverforge-0.5.0/src/runtime/mod.rs +0 -32
  196. solverforge-0.5.0/src/runtime/scalar_slots.rs +0 -29
  197. solverforge-0.5.0/src/runtime/static_fns.rs +0 -21
  198. solverforge-0.5.0/src/runtime/thread_local.rs +0 -21
  199. solverforge-0.5.0/src/schema/parse.rs +0 -260
  200. solverforge-0.5.0/src/schema/types.rs +0 -112
  201. solverforge-0.5.0/src/schema/validate.rs +0 -38
  202. solverforge-0.5.0/src/solver/solvable.rs +0 -60
  203. solverforge-0.5.0/src/state/callback_view.rs +0 -294
  204. solverforge-0.5.0/src/state/entity_table.rs +0 -51
  205. solverforge-0.5.0/src/state/marshal.rs +0 -349
  206. solverforge-0.5.0/src/state/solution.rs +0 -465
  207. solverforge-0.5.0/src/state/variables.rs +0 -22
  208. solverforge-0.5.0/tests/python/test_config.py +0 -196
  209. solverforge-0.5.0/tests/python/test_constraints.py +0 -638
  210. solverforge-0.5.0/tests/python/test_decorators.py +0 -53
  211. solverforge-0.5.0/tests/python/test_deliveries_example.py +0 -329
  212. solverforge-0.5.0/tests/python/test_deliveries_frontend_app.py +0 -255
  213. solverforge-0.5.0/tests/python/test_examples_import_surface.py +0 -63
  214. solverforge-0.5.0/tests/python/test_examples_playwright.py +0 -296
  215. solverforge-0.5.0/tests/python/test_hospital_example.py +0 -169
  216. solverforge-0.5.0/tests/python/test_hospital_frontend_app.py +0 -365
  217. solverforge-0.5.0/tests/python/test_list_solving.py +0 -1472
  218. solverforge-0.5.0/tests/python/test_manager.py +0 -82
  219. solverforge-0.5.0/tests/python/test_mixed_solving.py +0 -8
  220. solverforge-0.5.0/tests/python/test_release_metadata.py +0 -197
  221. solverforge-0.5.0/tests/python/test_scalar_solving.py +0 -1530
  222. solverforge-0.5.0/tests/python/test_threading.py +0 -11
  223. solverforge-0.5.0/tests/python/test_tracebacks.py +0 -40
  224. solverforge-0.5.0/tests/python/test_ui_assets.py +0 -125
  225. solverforge-0.5.0/tests/rust/constraint_set.rs +0 -38
  226. solverforge-0.5.0/tests/rust/descriptor.rs +0 -79
  227. solverforge-0.5.0/tests/rust/manager.rs +0 -6
  228. solverforge-0.5.0/tests/rust/runtime_slots.rs +0 -108
  229. solverforge-0.5.0/tests/rust/score.rs +0 -8
  230. solverforge-0.5.0/tests/rust/state_clone.rs +0 -226
  231. solverforge-0.5.0/tests/rust.rs +0 -16
  232. {solverforge-0.5.0 → solverforge-0.6.1}/LICENSE +0 -0
  233. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/console.py +0 -0
  234. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/errors.py +0 -0
  235. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/events.py +0 -0
  236. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/py.typed +0 -0
  237. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/score.py +0 -0
  238. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/typing.py +0 -0
  239. {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/ui.py +0 -0
  240. {solverforge-0.5.0 → solverforge-0.6.1}/rust-toolchain.toml +0 -0
  241. {solverforge-0.5.0 → solverforge-0.6.1}/src/callbacks/call.rs +0 -0
  242. {solverforge-0.5.0 → solverforge-0.6.1}/src/callbacks/mod.rs +0 -0
  243. {solverforge-0.5.0 → solverforge-0.6.1}/src/callbacks/registry.rs +0 -0
  244. {solverforge-0.5.0 → solverforge-0.6.1}/src/callbacks/threading.rs +0 -0
  245. {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/evaluate.rs +0 -0
  246. {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/incremental.rs +0 -0
  247. {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/matches.rs +0 -0
  248. {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/stream_plan.rs +0 -0
  249. {solverforge-0.5.0 → solverforge-0.6.1}/src/descriptor/extractor.rs +0 -0
  250. {solverforge-0.5.0 → solverforge-0.6.1}/src/descriptor/mod.rs +0 -0
  251. {solverforge-0.5.0 → solverforge-0.6.1}/src/descriptor/variable.rs +0 -0
  252. {solverforge-0.5.0 → solverforge-0.6.1}/src/intern.rs +0 -0
  253. {solverforge-0.5.0 → solverforge-0.6.1}/src/lib.rs +0 -0
  254. {solverforge-0.5.0 → solverforge-0.6.1}/src/proxy/entity.rs +0 -0
  255. {solverforge-0.5.0 → solverforge-0.6.1}/src/proxy/list.rs +0 -0
  256. {solverforge-0.5.0 → solverforge-0.6.1}/src/proxy/mod.rs +0 -0
  257. {solverforge-0.5.0 → solverforge-0.6.1}/src/proxy/solution.rs +0 -0
  258. {solverforge-0.5.0 → solverforge-0.6.1}/src/schema/build.rs +0 -0
  259. {solverforge-0.5.0 → solverforge-0.6.1}/src/schema/python.rs +0 -0
  260. {solverforge-0.5.0 → solverforge-0.6.1}/src/score.rs +0 -0
  261. {solverforge-0.5.0 → solverforge-0.6.1}/src/solver/mod.rs +0 -0
  262. {solverforge-0.5.0 → solverforge-0.6.1}/src/solver/progress.rs +0 -0
  263. {solverforge-0.5.0 → solverforge-0.6.1}/src/solver/result.rs +0 -0
  264. {solverforge-0.5.0 → solverforge-0.6.1}/src/state/clone.rs +0 -0
  265. {solverforge-0.5.0 → solverforge-0.6.1}/src/state/mod.rs +0 -0
  266. {solverforge-0.5.0 → solverforge-0.6.1}/src/ui.rs +0 -0
  267. {solverforge-0.5.0 → solverforge-0.6.1}/src/value.rs +0 -0
@@ -607,9 +607,9 @@ dependencies = [
607
607
 
608
608
  [[package]]
609
609
  name = "solverforge-bridge"
610
- version = "0.17.2"
610
+ version = "0.18.0"
611
611
  source = "registry+https://github.com/rust-lang/crates.io-index"
612
- checksum = "f1547aa9225350dcd821fa535f77c8048b13ceaf725e770f16ea3aa8e1eb4516"
612
+ checksum = "a9c592f73f90c14ca2af38b80ba6371ff4d41b6aaebafe341086225161836353"
613
613
  dependencies = [
614
614
  "solverforge-config",
615
615
  "solverforge-core",
@@ -619,9 +619,9 @@ dependencies = [
619
619
 
620
620
  [[package]]
621
621
  name = "solverforge-config"
622
- version = "0.17.2"
622
+ version = "0.18.0"
623
623
  source = "registry+https://github.com/rust-lang/crates.io-index"
624
- checksum = "8fc069e436715ac985d86e4e0ed2b0875ef2e8bf9f3db28d326d4861a4c4c1b3"
624
+ checksum = "0cb6e55b036aaff944d1c772ca6e51524c1dfe8dfa704342c2319b47665b8834"
625
625
  dependencies = [
626
626
  "serde",
627
627
  "serde_yaml",
@@ -632,9 +632,9 @@ dependencies = [
632
632
 
633
633
  [[package]]
634
634
  name = "solverforge-console"
635
- version = "0.17.2"
635
+ version = "0.18.0"
636
636
  source = "registry+https://github.com/rust-lang/crates.io-index"
637
- checksum = "17496619ea38be995def17793ad387d1fc4049a6f49a806d3b24a9accec074ee"
637
+ checksum = "e100997cd6d2fbafc8eb8e1d84bf60149a1386b89e8562a81d9408085a00520c"
638
638
  dependencies = [
639
639
  "num-format",
640
640
  "owo-colors",
@@ -644,18 +644,18 @@ dependencies = [
644
644
 
645
645
  [[package]]
646
646
  name = "solverforge-core"
647
- version = "0.17.2"
647
+ version = "0.18.0"
648
648
  source = "registry+https://github.com/rust-lang/crates.io-index"
649
- checksum = "2fd99a8ef8afe51a1958fa886a38b46e2029298b738f5a10635caa80450d72d2"
649
+ checksum = "d922c9a0ee10e0ba68c7c9b98e38d07399820358dc0d10d238a9e6641ef45d27"
650
650
  dependencies = [
651
651
  "thiserror",
652
652
  ]
653
653
 
654
654
  [[package]]
655
655
  name = "solverforge-scoring"
656
- version = "0.17.2"
656
+ version = "0.18.0"
657
657
  source = "registry+https://github.com/rust-lang/crates.io-index"
658
- checksum = "09a3c8598734bee86f3d60b9994febae8d9062a0243ae638f63417a7344cc41d"
658
+ checksum = "40a4b4eb8031ba04556106ae1bb13533c4f50ab206b3c5844c4e0284e42bd4f2"
659
659
  dependencies = [
660
660
  "solverforge-core",
661
661
  "thiserror",
@@ -663,9 +663,9 @@ dependencies = [
663
663
 
664
664
  [[package]]
665
665
  name = "solverforge-solver"
666
- version = "0.17.2"
666
+ version = "0.18.0"
667
667
  source = "registry+https://github.com/rust-lang/crates.io-index"
668
- checksum = "79b472d2acf63b5dc284bbfec4800cdd476908fe5e5c0f33141b30bf1c37d584"
668
+ checksum = "7a5b5a898495984107de03cdc9c151c39ec9b4d70cfa509950cbe58c263953a4"
669
669
  dependencies = [
670
670
  "rand",
671
671
  "rand_chacha",
@@ -691,7 +691,7 @@ dependencies = [
691
691
 
692
692
  [[package]]
693
693
  name = "solverforge_py"
694
- version = "0.5.0"
694
+ version = "0.6.1"
695
695
  dependencies = [
696
696
  "indexmap",
697
697
  "once_cell",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "solverforge_py"
3
- version = "0.5.0"
3
+ version = "0.6.1"
4
4
  edition = "2021"
5
5
  license = "Apache-2.0"
6
6
  rust-version = "1.95"
@@ -9,13 +9,24 @@ readme = "README.md"
9
9
  repository = "https://github.com/SolverForge/solverforge-py"
10
10
  homepage = "https://solverforge.org"
11
11
  documentation = "https://docs.solverforge.org"
12
+ include = [
13
+ "/Cargo.lock",
14
+ "/LICENSE",
15
+ "/README.md",
16
+ "/pyproject.toml",
17
+ "/python/solverforge/*.py",
18
+ "/python/solverforge/*.pyi",
19
+ "/python/solverforge/py.typed",
20
+ "/rust-toolchain.toml",
21
+ "/src/**",
22
+ ]
12
23
 
13
24
  [lib]
14
25
  name = "solverforge_py"
15
26
  crate-type = ["cdylib", "rlib"]
16
27
 
17
28
  [package.metadata.solverforge]
18
- version = "0.17.2"
29
+ version = "0.18.0"
19
30
 
20
31
  [dependencies]
21
32
  indexmap = "2.12.1"
@@ -31,12 +42,12 @@ toml = "0.9.8"
31
42
  tracing = "0.1.44"
32
43
  tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
33
44
 
34
- solverforge-config = { version = "=0.17.2" }
35
- solverforge-bridge = { version = "=0.17.2" }
36
- solverforge-console = { version = "=0.17.2", features = ["verbose-logging"] }
37
- solverforge-core = { version = "=0.17.2" }
38
- solverforge-scoring = { version = "=0.17.2" }
39
- solverforge-solver = { version = "=0.17.2" }
45
+ solverforge-config = { version = "=0.18.0" }
46
+ solverforge-bridge = { version = "=0.18.0" }
47
+ solverforge-console = { version = "=0.18.0", features = ["verbose-logging"] }
48
+ solverforge-core = { version = "=0.18.0" }
49
+ solverforge-scoring = { version = "=0.18.0" }
50
+ solverforge-solver = { version = "=0.18.0" }
40
51
  solverforge-ui = { version = "=0.7.0", default-features = false }
41
52
 
42
53
  [dev-dependencies]
@@ -0,0 +1,409 @@
1
+ Metadata-Version: 2.4
2
+ Name: solverforge
3
+ Version: 0.6.1
4
+ Classifier: Development Status :: 3 - Alpha
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: Intended Audience :: Science/Research
7
+ Classifier: License :: OSI Approved :: Apache Software License
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.14
11
+ Classifier: Programming Language :: Python :: Implementation :: CPython
12
+ Classifier: Programming Language :: Rust
13
+ Classifier: Typing :: Typed
14
+ Classifier: Topic :: Scientific/Engineering
15
+ Requires-Dist: fastapi>=0.115,<1 ; extra == 'examples'
16
+ Requires-Dist: uvicorn>=0.32,<1 ; extra == 'examples'
17
+ Provides-Extra: examples
18
+ License-File: LICENSE
19
+ Summary: Dynamic Python bindings for the SolverForge constraint solver.
20
+ Keywords: constraint-solving,optimization,planning,solverforge
21
+ Home-Page: https://solverforge.org
22
+ Author: SolverForge
23
+ License: Apache-2.0
24
+ Requires-Python: >=3.14
25
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
26
+ Project-URL: Bug Tracker, https://github.com/SolverForge/solverforge-py/issues
27
+ Project-URL: Changelog, https://github.com/SolverForge/solverforge-py/releases
28
+ Project-URL: Documentation, https://docs.solverforge.org
29
+ Project-URL: Homepage, https://solverforge.org
30
+ Project-URL: Repository, https://github.com/SolverForge/solverforge-py
31
+
32
+ # SolverForge Python
33
+
34
+ `solverforge` is the dynamic Python binding package for SolverForge. Python
35
+ users define models with Python classes, decorators, functions, and lambdas.
36
+ They do not write Rust and they do not pass JSON to a fixed backend.
37
+
38
+ The native extension owns the working solution state in Rust so SolverForge can
39
+ clone, mutate, and snapshot solutions safely. Python callbacks are the single
40
+ constraint authoring surface.
41
+
42
+ The package targets CPython 3.14 and Rust 1.95.0. This checkout consumes the
43
+ published SolverForge Rust crates `0.18.0` and pins `solverforge-ui` to `0.7.0`
44
+ through exact crates.io versions in `Cargo.toml` and registry checksums in
45
+ `Cargo.lock`; it has no local path or Git dependency override. The `solverforge`
46
+ `0.6.x` line is the current dynamic binding architecture and intentionally
47
+ supersedes the older incompatible `0.2.x` and `0.3.0` artifacts in the same PyPI
48
+ namespace. Those older artifacts exposed `SolverFactory`, `PlanningVariable`,
49
+ Java service requirements, and other APIs that are not part of this package.
50
+
51
+ ## Installation
52
+
53
+ Install the `solverforge` `0.6.1` release, built on the exact published and
54
+ locked SolverForge Rust `0.18.0` registry dependency base:
55
+
56
+ ```sh
57
+ python3.14 -m pip install "solverforge==0.6.1"
58
+ ```
59
+
60
+ The installable wheel contains the core `solverforge` package, native extension,
61
+ and embedded shared `solverforge-ui` assets exposed through
62
+ `solverforge.ui.asset()` and `solverforge.ui.asset_paths()`.
63
+ Source-checkout examples, including the hospital and deliveries FastAPI apps and
64
+ their app-specific static files, are maintained in this repository rather than
65
+ installed into the runtime wheel.
66
+
67
+ ## Examples
68
+
69
+ Run source-current examples through the root Makefile:
70
+
71
+ ```sh
72
+ make hospital-run
73
+ make deliveries-run PORT=7861
74
+ ```
75
+
76
+ The same source-checkout examples can be run against the installed package
77
+ without `PYTHONPATH` or an editable checkout:
78
+
79
+ ```sh
80
+ python3.14 -m venv .venv-examples
81
+ . .venv-examples/bin/activate
82
+ python -m pip install "solverforge[examples]==0.6.1"
83
+ python examples/nqueens.py
84
+ python -m examples.solverforge_hospital
85
+ python -m examples.solverforge_deliveries
86
+ ```
87
+
88
+ Then open `http://127.0.0.1:7860` for the hospital app or
89
+ `http://127.0.0.1:7861` for the deliveries app.
90
+
91
+ For local package development, use the root `Makefile` targets instead.
92
+
93
+ ## Minimal Model
94
+
95
+ A minimal model uses Python decorators for domain metadata and plain Python
96
+ callbacks for constraints:
97
+
98
+ ```python
99
+ from solverforge import (
100
+ ConstraintFactory,
101
+ HardSoftScore,
102
+ Solver,
103
+ constraint_provider,
104
+ planning_entity,
105
+ planning_solution,
106
+ planning_variable,
107
+ )
108
+
109
+ @planning_entity
110
+ class Shift:
111
+ nurse = planning_variable(value_range_provider="nurses", allows_unassigned=True)
112
+
113
+ def __init__(self, required: bool = True, nurse: int | None = None) -> None:
114
+ self.required = required
115
+ self.nurse = nurse
116
+
117
+ @constraint_provider
118
+ def constraints(factory: ConstraintFactory):
119
+ return [
120
+ factory.for_each(Shift)
121
+ .filter(lambda shift: shift.required and shift.nurse is None)
122
+ .penalize(HardSoftScore.ONE_HARD)
123
+ .named("required shift is unassigned")
124
+ ]
125
+
126
+ @planning_solution(score=HardSoftScore, constraints=constraints)
127
+ class Schedule:
128
+ shifts: list[Shift]
129
+
130
+ def __init__(self, shifts: list[Shift], nurses: list[int]) -> None:
131
+ self.shifts = shifts
132
+ self.nurses = nurses
133
+ self.score = None
134
+
135
+ solution = Solver.solve(Schedule([Shift(), Shift()], [0, 1]))
136
+ ```
137
+
138
+ ## Development
139
+
140
+ Local development is driven by the root `Makefile`, which creates `.venv`,
141
+ installs maturin and developer tools, and builds the PyO3 extension against the
142
+ current checkout.
143
+
144
+ ```sh
145
+ make develop # release native extension installed into .venv
146
+ make install-playwright-system-deps # Linux Chromium browser libraries for CI
147
+ make test # Rust tests with Python link setup, then pytest
148
+ make lint # rustfmt check, ruff, mypy, and clippy
149
+ make ci-local # local CI simulation
150
+ make pre-release # ci-local plus release sdist/wheel checks
151
+ ```
152
+
153
+ Run `make help` for focused targets such as `make test-hospital`,
154
+ `make test-deliveries`, `make test-examples-browser`, `make docs-check`,
155
+ `make release-base-check`, `make test-one TEST=pattern`, `make hospital-run`,
156
+ `make hospital-solve`, `make deliveries-run`, and `make deliveries-solve`.
157
+
158
+ ## Boundaries
159
+
160
+ - No generated Rust.
161
+ - No expression-object DSL.
162
+ - No string-parsed constraints.
163
+ - No duplicate 100% Python solver.
164
+ - No private upstream SolverForge modules; bindings use the public dynamic bridge
165
+ contract.
166
+ - No fixed pre-modeled JSON-only backends.
167
+ - Public bridge work belongs upstream; the wrapper consumes only public seams
168
+ rather than private upstream modules or a wrapper-owned solver path.
169
+
170
+ ## Current Support
171
+
172
+ - `Solver.solve(..., config=None)` and `SolverManager(config=None)` load a
173
+ user-space `solver.toml` from the current directory when present. Explicit
174
+ `SolverConfig` or `dict` configs are normalized before Rust handoff, and
175
+ top-level plus phase-level termination fields match upstream SolverForge:
176
+ `seconds_spent_limit`, `minutes_spent_limit`, `best_score_limit`,
177
+ `step_count_limit`, `unimproved_step_count_limit`, and
178
+ `unimproved_seconds_spent_limit`.
179
+ - Synchronous and retained scalar/list construction solves use one upstream
180
+ compiled runtime runner. Ordinary dynamic scalar construction supports
181
+ `first_fit` and `cheapest_insertion`. A declared assignment group additionally
182
+ supports `first_fit_decreasing`, `weakest_fit`, `weakest_fit_decreasing`,
183
+ `strongest_fit`, and `strongest_fit_decreasing`; the decreasing variants
184
+ require `entity_order`, while weakest/strongest variants require
185
+ `value_order`. Dynamic list construction supports `list_round_robin`,
186
+ `list_cheapest_insertion`, `list_regret_insertion`, `list_clarke_wright`, and
187
+ `list_k_opt`, with the final two requiring their respective savings and route
188
+ metadata bundles. These are core graph nodes over the immutable runtime
189
+ model. An explicit `group_name` phase obeys its configured obligation and
190
+ termination; required-only completion belongs solely to upstream omitted
191
+ defaults. Default local search is assembled only when the top-level
192
+ termination has an effective finite limit, so an empty or invalid termination
193
+ cannot accidentally create an unbounded solve. There is no wrapper assignment
194
+ placer, required stream, phase tree, TLS slot state, synthetic metric, or
195
+ fallback path.
196
+ - `SolverManager` is backed by upstream retained jobs, statuses, events, and
197
+ snapshots. `SolverManager.solve(...)` returns `JobHandle(job_id=...)`; the
198
+ manager supports pause, resume, cancel, delete, and exact snapshot reads.
199
+ Retained event payloads read current score from the attached solution snapshot
200
+ when present. Long-running phases publish progress at the shared upstream
201
+ cadence, and telemetry includes the current phase type, index, counters, and
202
+ generation/evaluation time. `telemetry_detail(job_id)` returns one atomic
203
+ `status` with detailed telemetry plus an optional `candidate_trace`; ordinary
204
+ status/event polling remains compact.
205
+ - Candidate tracing is an opt-in retained-job diagnostic configured as
206
+ `candidate_trace = { max_entries = N }`, where `N` must be non-zero. The core
207
+ records the total pull count after the bounded identity prefix is full and
208
+ marks truncation explicitly. Synchronous `Solver.solve(...)` rejects
209
+ `candidate_trace` because only `SolverManager.telemetry_detail(...)` can return
210
+ it. Trace format 3 exposes canonical configured input, execution policy,
211
+ resolved phase-plan provenance, candidate identities and dispositions, prefix
212
+ digests, completeness flags, and the explicit
213
+ `candidate_index_scope = "source_local_only"` contract.
214
+ `QualifiedCandidateTraceProvenance(...)` accepts keyword-only lowercase
215
+ SHA-256 values for the schema, instance, initial state, core tree, and loaded
216
+ build plus a non-blank external producer. Pass it per job as
217
+ `SolverManager.solve(..., qualified_candidate_trace_provenance=...)`; it is
218
+ rejected before schema discovery unless that manager has candidate tracing
219
+ enabled. The provenance is never inferred from a solution or accepted through
220
+ serializable solver config, and ordinary traces remain explicitly
221
+ `not_requested` rather than being upgraded by the presence of digest fields.
222
+ - `@candidate_metric("name")` registers a named numeric move-ranking callback
223
+ through `@planning_solution(..., candidate_metrics=[...])`. A metric receives
224
+ a read-only solution callback view and the core candidate's canonical logical
225
+ identity. Leaf selectors with `selection_order = "sorted"` or
226
+ `"probabilistic"` must name a registered `selection_metric`; other selection
227
+ orders reject that field. Sorted metrics are ascending. Metric values must be
228
+ finite, and probabilistic weights must also be non-negative, with zero-weight
229
+ candidates omitted.
230
+ - `planning_variable(...)` supports row candidate callbacks and nearby value or
231
+ entity candidate/distance metadata for dynamic scalar construction and nearby
232
+ scalar local search. Each nearby metadata parameter has exactly one source:
233
+ either a Python callback or a row field name. Dual raw-schema sources are
234
+ rejected at compilation, while immutable row metadata can be supplied without
235
+ per-query Python callbacks. Provider-backed value ranges are imported once per
236
+ variable and shared across rows in Rust-owned state; row candidate callbacks
237
+ remain row-specific.
238
+ - `scalar_assignment_group(...)` declares assignment-aware scalar groups for
239
+ grouped scalar local search and assignment-group construction. Group metadata
240
+ covers required entities, capacity keys, assignment rules, ordering callbacks,
241
+ callback synchronization policy, and `ScalarGroupLimits`. Assignment callbacks
242
+ receive a solution view whose entity/fact collections reflect Rust-owned
243
+ planning state and whose ordinary solution-level attributes remain available
244
+ for read-only lookup context such as capacity tables. For invariant row
245
+ metadata, `required_entity_field`, `capacity_key_field`,
246
+ `position_key_field`, and `sequence_key_field` are explicit alternatives to
247
+ their callbacks: required is a row bool, position and sequence are row
248
+ integers, and capacity is a row list indexed by candidate value. A field
249
+ alternative is mutually exclusive with its callback and avoids
250
+ per-candidate Python transitions without caching or changing callback behavior.
251
+ The compiled schema retains one immutable runtime plan (schema, descriptor,
252
+ and model), reused by direct solves, manager jobs, snapshots, and resumes;
253
+ instance rows, callback views, seeds, and moves remain per solve.
254
+ An assignment-owned planning variable is exclusively edited through its
255
+ declared grouped construction and `grouped_scalar_move_selector`; a raw
256
+ scalar, nearby, ruin, or conflict-repair selector targeting that same
257
+ variable is rejected instead of creating a second search path. Generic
258
+ `@scalar_group` and conflict-repair callbacks are rejected in a local-search
259
+ phase that could reach an assignment-owned slot: their returned edits are
260
+ unscoped, so accepting them would recreate a second ownership path. Multiple
261
+ declarative assignment groups can still compose in selector combinators.
262
+ - `planning_list_variable(...)` supports `element_owner`,
263
+ `construction_element_order_key`, `precedence_duration`, and
264
+ `precedence_successors` for owner-aware list moves and JSSP-style
265
+ precedence/makespan scoring. Its route-related metadata has one canonical,
266
+ nested declaration form: `route=ListRouteHooks(...)`,
267
+ `savings=ListSavingsHooks(...)`, plus independent
268
+ `cross_position_distance=` and `intra_position_distance=` sources. Route and
269
+ savings are complete, independent bundles: a route bundle never implicitly
270
+ enables savings construction.
271
+ The owner, construction-order, duration, and successor metadata parameters
272
+ each accept either a Python callback or the name of a solution-level sequence
273
+ indexed by element ID; string sources let the native runtime read immutable
274
+ element metadata without per-element Python calls. A declared sequence is
275
+ mandatory before state import: owner entries are `None` or non-negative
276
+ integers, order entries are integers, durations are non-negative integers,
277
+ and successor entries are ordered sequences of non-negative integers. Missing
278
+ or malformed sequences fail import; they never become an unrestricted owner,
279
+ default order, or missing precedence edge. Nested route sources use explicit
280
+ source wrappers rather than unscoped strings or callbacks.
281
+ In a nested list bundle, `RowField("...")`, `SolutionField("...")`,
282
+ `EntityCallback(...)`, and `SolutionCallback(...)` make the source scope
283
+ explicit. `RowField` reads only the declared entity row; `SolutionField` reads
284
+ only the declared immutable solution-root value. Neither falls back to the
285
+ other scope. Capacity feasibility is an explicit
286
+ `CapacityRouteFeasibility(capacity=..., demand=...)` source whose two fields
287
+ are independently scoped. A new nested declaration therefore never infers
288
+ callback scope from arity or shares a route source with savings.
289
+
290
+ Nearby list neighborhoods require their own
291
+ `cross_position_distance` and `intra_position_distance` sources, and neither
292
+ is inferred from a route distance. A matrix source for either metric is
293
+ indexed by the actual list values at the two positions, never by their row or
294
+ position indexes.
295
+ `shadow_variable_updates(...)` registers post-update listeners whose
296
+ native-owned fields are refreshed during solve/analyze and exported back to
297
+ Python objects. Attached callback solution views synchronize dirty rows after
298
+ the first full view sync instead of rewriting every entity row for repeated
299
+ full-solution callbacks.
300
+ - Supported score families are `SoftScore`, `HardSoftScore`,
301
+ `HardSoftDecimalScore`, and `HardMediumSoftScore`.
302
+ - Python callback constraints are evaluated from Rust-owned dynamic state.
303
+ Supported stream shapes are unary `for_each(...).filter(...)`, binary
304
+ `for_each(...).join(...).filter(...)`, and grouped-count
305
+ `for_each(...).group_by(...)`, plus `for_each(...).balance(...)`,
306
+ `for_each_unassigned_element(owner_entity_type, variable_name)`, and
307
+ `list_precedence_makespan(owner_entity_type, variable_name)`. Ordinary
308
+ penalize/reward streams support fixed or callback-computed score weights; list
309
+ precedence/makespan scoring is computed natively from owner, duration, and
310
+ successor metadata callbacks or field-name metadata. `indexed_presence(...)`
311
+ is available as a grouped collector for run/range presence scoring.
312
+ `joiner.equal(...)` and `joiner.equal_bi(...)` accept either Python key
313
+ callbacks or attribute-name strings. Callback keys preserve Python equality
314
+ semantics. String keys specialize to native equality only for planning scalar
315
+ slots and stable fields stored directly on every imported row; properties,
316
+ shadow-derived attributes, containers, and unsupported scalar values use live
317
+ Python attribute lookup and equality.
318
+ Schema/runtime plans reuse only capture-free functions with canonical module
319
+ namespace provenance and no defaults or function-owned metadata. A callback
320
+ from a different module namespace never shares a plan merely because its code
321
+ object matches; mutable values in the same callback namespace remain live.
322
+ Closures, bound defaults, partials, methods, callable instances, and other
323
+ stateful callbacks compile per invocation rather than being retained across solves.
324
+ Constraint plans specialize after state import; simple fixed-weight unary,
325
+ list-unassigned, and proven string-key equality joins can evaluate directly
326
+ from Rust-owned state while callback filters, callback weights, computed
327
+ attributes, and unsupported key values use the Python callback surface.
328
+ - Dynamic scalar local search is available through upstream-style dynamic
329
+ `change_move_selector`, `swap_move_selector`, `nearby_change_move_selector`,
330
+ `nearby_swap_move_selector`, `pillar_change_move_selector`,
331
+ `pillar_swap_move_selector`, `ruin_recreate_move_selector`,
332
+ `grouped_scalar_move_selector`, `conflict_repair_move_selector`, and
333
+ `compound_conflict_repair_move_selector` phases.
334
+ Raw scalar selectors exclude variables owned by `scalar_assignment_group(...)`;
335
+ use that group's `grouped_scalar_move_selector` for its assignment variable.
336
+ - Dynamic list local search is available through upstream-style
337
+ `list_change_move_selector`, `nearby_list_change_move_selector`,
338
+ `list_swap_move_selector`, `nearby_list_swap_move_selector`,
339
+ `sublist_change_move_selector`, `sublist_swap_move_selector`,
340
+ `list_reverse_move_selector`, `list_permute_move_selector`,
341
+ `list_precedence_move_selector`, `k_opt_move_selector`, and
342
+ `list_ruin_move_selector` phases.
343
+ - `limited_neighborhood`, `union_move_selector`, and two-child
344
+ `cartesian_product_move_selector` compose supported dynamic scalar and list
345
+ selectors. Dynamic neighborhoods are opened as resumable cursor trees: union
346
+ children, limits, Cartesian branches, pillar windows, and k-opt reconnections
347
+ advance only when the solver requests the next candidate. Nearby candidate
348
+ callbacks may return any Python iterable; candidates are consumed once into
349
+ an exact bounded top-k rather than copied into a full ranked neighborhood.
350
+ Stable candidate IDs remain borrowable for evaluation, losing candidates are
351
+ released as soon as the forager no longer needs them, and only the selected
352
+ move crosses the ownership boundary. Exact sorting, shuffling, and probability
353
+ decorators retain the compact global ordering data their documented semantics
354
+ require.
355
+ - `examples/solverforge_hospital` is a 1:1 Python model of the Rust hospital
356
+ use case's public dataset/config surface: `LARGE` has 50 employees, 688
357
+ initially unassigned shifts, skill/availability-aware construction candidates,
358
+ retained jobs, snapshots, analysis, pause/resume/cancel, and a reproducible
359
+ 30-second hard-feasible terminal solve when installed with the release native
360
+ extension.
361
+ - `examples/solverforge_deliveries` is the Python port of the deliveries use
362
+ case: initially unassigned delivery facts, route-owning vehicles, independent
363
+ row-backed `ListRouteHooks` and `ListSavingsHooks` bundles with
364
+ solution-scoped feasibility, route shadow metrics, unassigned-delivery
365
+ scoring, retained jobs, route snapshots, analysis, and delivery-insertion
366
+ recommendations.
367
+ - Top-level `ConstraintFactory.join`, `group_by`, `if_exists`, `if_not_exists`,
368
+ and `flattened` remain explicit unsupported methods until the native callback
369
+ stream planner has public bridge support for those top-level semantics. Use
370
+ stream-level `for_each(...).join(...)` and `for_each(...).group_by(...)` for
371
+ the supported join and grouped-count surfaces.
372
+
373
+ Rust macro-generated SolverForge models remain the performance ceiling. The
374
+ Python path preserves the Rust solver engine and Rust-owned state while paying
375
+ the honest dynamic callback boundary cost.
376
+
377
+ ## Callback And Threading Contract
378
+
379
+ Python callbacks are the only constraint authoring surface. Filter callbacks
380
+ return `bool`; weight callbacks return a score object or integer; grouped scalar
381
+ callbacks return scalar edit candidates for `@scalar_group`; conflict repair
382
+ callbacks return scalar edit candidates for constraints declared with
383
+ `@conflict_repair`; candidate-metric callbacks return finite numeric ranking
384
+ values; value range providers return ordered finite collections; nearby
385
+ candidate callbacks return Python iterables; distance callbacks return numeric
386
+ distances; and owner hooks used by list selectors return an owner index or
387
+ `None`. Candidate metrics receive the callback solution view plus an operation
388
+ or composite identity dictionary; they rank an already generated core move and
389
+ do not rebuild or execute a second selector.
390
+
391
+ Callback exceptions are surfaced as SolverForge Python exceptions with the
392
+ original Python traceback. Callback solution views include non-private,
393
+ non-callable solution-level attributes from the imported Python solution, such as
394
+ lookup tables and value sets used by hooks. Entity and fact collections in those
395
+ views are projected from Rust-owned solver state so preview and best-solution
396
+ clones never share mutable Python row objects with the working solution.
397
+
398
+ The primary target is CPython 3.14 free-threaded. The native extension declares
399
+ that it does not require the GIL; Rust worker threads attach to Python only when
400
+ invoking Python callbacks. Callback code must be deterministic for a given
401
+ solution state, safe to run concurrently, and treat solution-level lookup context
402
+ as immutable for the duration of a solve. Third-party Python extension modules
403
+ used inside callbacks may still impose their own synchronization constraints.
404
+
405
+ ## License
406
+
407
+ SolverForge Python is licensed under the Apache License, Version 2.0. See
408
+ `LICENSE` for the full license text.
409
+