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.
- {solverforge-0.5.0 → solverforge-0.6.1}/Cargo.lock +13 -13
- {solverforge-0.5.0 → solverforge-0.6.1}/Cargo.toml +19 -8
- solverforge-0.6.1/PKG-INFO +409 -0
- solverforge-0.6.1/README.md +377 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/pyproject.toml +1 -1
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/__init__.py +26 -2
- solverforge-0.6.1/python/solverforge/_native.pyi +72 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/config.py +27 -3
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/constraints.py +50 -18
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/decorators.py +102 -14
- solverforge-0.6.1/python/solverforge/fields.py +494 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/groups.py +70 -2
- solverforge-0.6.1/python/solverforge/joiner.py +39 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/manager.py +31 -5
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/model.py +167 -6
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/solver.py +6 -4
- {solverforge-0.5.0 → solverforge-0.6.1}/src/bindings.rs +6 -0
- solverforge-0.6.1/src/config.rs +113 -0
- solverforge-0.6.1/src/constraints/compiled.rs +322 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/list_precedence.rs +222 -54
- {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/mod.rs +1 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/state.rs +411 -105
- solverforge-0.6.1/src/error.rs +44 -0
- solverforge-0.6.1/src/manager/candidate_trace.rs +388 -0
- solverforge-0.6.1/src/manager/events.rs +564 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/manager/jobs.rs +102 -27
- solverforge-0.6.1/src/manager/mod.rs +7 -0
- solverforge-0.6.1/src/manager/provenance.rs +205 -0
- solverforge-0.6.1/src/runtime/candidate_metric.rs +119 -0
- solverforge-0.6.1/src/runtime/dynamic_assignment_group.rs +471 -0
- solverforge-0.6.1/src/runtime/dynamic_provider.rs +291 -0
- solverforge-0.6.1/src/runtime/list_slots.rs +798 -0
- solverforge-0.6.1/src/runtime/mod.rs +46 -0
- solverforge-0.6.1/src/runtime/scalar_slots.rs +337 -0
- solverforge-0.6.1/src/schema/compiled.rs +43 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/schema/mod.rs +2 -0
- solverforge-0.6.1/src/schema/parse.rs +760 -0
- solverforge-0.6.1/src/schema/runtime_plan.rs +71 -0
- solverforge-0.6.1/src/schema/types.rs +266 -0
- solverforge-0.6.1/src/schema/validate.rs +118 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/solver/api.rs +29 -51
- {solverforge-0.5.0 → solverforge-0.6.1}/src/solver/console.rs +14 -15
- solverforge-0.6.1/src/solver/solvable.rs +53 -0
- solverforge-0.6.1/src/state/callback_view.rs +616 -0
- solverforge-0.6.1/src/state/entity_table.rs +143 -0
- solverforge-0.6.1/src/state/marshal.rs +1195 -0
- solverforge-0.6.1/src/state/solution.rs +844 -0
- solverforge-0.6.1/src/state/variables.rs +18 -0
- solverforge-0.5.0/.github/workflows/ci.yml +0 -69
- solverforge-0.5.0/.github/workflows/release.yml +0 -200
- solverforge-0.5.0/.gitignore +0 -11
- solverforge-0.5.0/.pre-commit-config.yaml +0 -31
- solverforge-0.5.0/.python-version +0 -1
- solverforge-0.5.0/AGENTS.md +0 -121
- solverforge-0.5.0/CHANGELOG.md +0 -53
- solverforge-0.5.0/Makefile +0 -408
- solverforge-0.5.0/PKG-INFO +0 -336
- solverforge-0.5.0/README.md +0 -304
- solverforge-0.5.0/WIREFRAME.md +0 -306
- solverforge-0.5.0/examples/list_tsp.py +0 -20
- solverforge-0.5.0/examples/mixed_job_shop.py +0 -30
- solverforge-0.5.0/examples/nqueens.py +0 -36
- solverforge-0.5.0/examples/shift_scheduling.py +0 -45
- solverforge-0.5.0/examples/solverforge_deliveries/README.md +0 -85
- solverforge-0.5.0/examples/solverforge_deliveries/__init__.py +0 -23
- solverforge-0.5.0/examples/solverforge_deliveries/__main__.py +0 -32
- solverforge-0.5.0/examples/solverforge_deliveries/solver.toml +0 -51
- solverforge-0.5.0/examples/solverforge_deliveries/src/__init__.py +0 -1
- solverforge-0.5.0/examples/solverforge_deliveries/src/api/__init__.py +0 -1
- solverforge-0.5.0/examples/solverforge_deliveries/src/api/dto.py +0 -215
- solverforge-0.5.0/examples/solverforge_deliveries/src/api/mod.py +0 -3
- solverforge-0.5.0/examples/solverforge_deliveries/src/api/routes.py +0 -239
- solverforge-0.5.0/examples/solverforge_deliveries/src/api/sse.py +0 -42
- solverforge-0.5.0/examples/solverforge_deliveries/src/constraints/__init__.py +0 -5
- solverforge-0.5.0/examples/solverforge_deliveries/src/constraints/mod.py +0 -47
- solverforge-0.5.0/examples/solverforge_deliveries/src/data/__init__.py +0 -5
- solverforge-0.5.0/examples/solverforge_deliveries/src/data/data_seed/__init__.py +0 -5
- solverforge-0.5.0/examples/solverforge_deliveries/src/data/data_seed/entrypoints.py +0 -213
- solverforge-0.5.0/examples/solverforge_deliveries/src/data/data_seed/locations.json +0 -1472
- solverforge-0.5.0/examples/solverforge_deliveries/src/data/mod.py +0 -1
- solverforge-0.5.0/examples/solverforge_deliveries/src/domain/__init__.py +0 -33
- solverforge-0.5.0/examples/solverforge_deliveries/src/domain/delivery.py +0 -32
- solverforge-0.5.0/examples/solverforge_deliveries/src/domain/metrics.py +0 -404
- solverforge-0.5.0/examples/solverforge_deliveries/src/domain/mod.py +0 -1
- solverforge-0.5.0/examples/solverforge_deliveries/src/domain/plan.py +0 -84
- solverforge-0.5.0/examples/solverforge_deliveries/src/domain/vehicle.py +0 -41
- solverforge-0.5.0/examples/solverforge_deliveries/src/lib.py +0 -34
- solverforge-0.5.0/examples/solverforge_deliveries/src/main.py +0 -22
- solverforge-0.5.0/examples/solverforge_deliveries/src/solver/__init__.py +0 -5
- solverforge-0.5.0/examples/solverforge_deliveries/src/solver/mod.py +0 -1
- solverforge-0.5.0/examples/solverforge_deliveries/src/solver/service/__init__.py +0 -196
- solverforge-0.5.0/examples/solverforge_deliveries/src/solver/service/mod.py +0 -7
- solverforge-0.5.0/examples/solverforge_deliveries/src/solver/service/payload.py +0 -86
- solverforge-0.5.0/examples/solverforge_deliveries/static/app/main.mjs +0 -287
- solverforge-0.5.0/examples/solverforge_deliveries/static/app/models/core.mjs +0 -57
- solverforge-0.5.0/examples/solverforge_deliveries/static/app/models/formatters.mjs +0 -53
- solverforge-0.5.0/examples/solverforge_deliveries/static/app/models/preview.mjs +0 -154
- solverforge-0.5.0/examples/solverforge_deliveries/static/app/models/timeline.mjs +0 -94
- solverforge-0.5.0/examples/solverforge_deliveries/static/app/models.mjs +0 -8
- solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/api-guide.mjs +0 -29
- solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/components.mjs +0 -44
- solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/data-tables.mjs +0 -98
- solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/layout.mjs +0 -123
- solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/lifecycle.mjs +0 -23
- solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/modals.mjs +0 -63
- solverforge-0.5.0/examples/solverforge_deliveries/static/app/ui/overview.mjs +0 -172
- solverforge-0.5.0/examples/solverforge_deliveries/static/app.css +0 -196
- solverforge-0.5.0/examples/solverforge_deliveries/static/app.js +0 -3
- solverforge-0.5.0/examples/solverforge_deliveries/static/generated/ui-model.json +0 -25
- solverforge-0.5.0/examples/solverforge_deliveries/static/index.html +0 -22
- solverforge-0.5.0/examples/solverforge_deliveries/static/sf-config.json +0 -4
- solverforge-0.5.0/examples/solverforge_hospital/README.md +0 -81
- solverforge-0.5.0/examples/solverforge_hospital/__init__.py +0 -43
- solverforge-0.5.0/examples/solverforge_hospital/__main__.py +0 -32
- solverforge-0.5.0/examples/solverforge_hospital/solver.toml +0 -38
- solverforge-0.5.0/examples/solverforge_hospital/src/__init__.py +0 -41
- solverforge-0.5.0/examples/solverforge_hospital/src/api/__init__.py +0 -20
- solverforge-0.5.0/examples/solverforge_hospital/src/api/dto.py +0 -281
- solverforge-0.5.0/examples/solverforge_hospital/src/api/mod.py +0 -20
- solverforge-0.5.0/examples/solverforge_hospital/src/api/routes.py +0 -193
- solverforge-0.5.0/examples/solverforge_hospital/src/api/sse.py +0 -42
- solverforge-0.5.0/examples/solverforge_hospital/src/constraints/__init__.py +0 -3
- solverforge-0.5.0/examples/solverforge_hospital/src/constraints/assigned_shift.py +0 -14
- solverforge-0.5.0/examples/solverforge_hospital/src/constraints/balance_assignments.py +0 -20
- solverforge-0.5.0/examples/solverforge_hospital/src/constraints/desired_day.py +0 -24
- solverforge-0.5.0/examples/solverforge_hospital/src/constraints/minimum_rest.py +0 -33
- solverforge-0.5.0/examples/solverforge_hospital/src/constraints/mod.py +0 -42
- solverforge-0.5.0/examples/solverforge_hospital/src/constraints/one_shift_per_day.py +0 -16
- solverforge-0.5.0/examples/solverforge_hospital/src/constraints/overlapping_shift.py +0 -28
- solverforge-0.5.0/examples/solverforge_hospital/src/constraints/required_skill.py +0 -20
- solverforge-0.5.0/examples/solverforge_hospital/src/constraints/unavailable_employee.py +0 -32
- solverforge-0.5.0/examples/solverforge_hospital/src/constraints/undesired_day.py +0 -24
- solverforge-0.5.0/examples/solverforge_hospital/src/data/__init__.py +0 -23
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/LARGE.json +0 -1
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/__init__.py +0 -13
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/availability.py +0 -16
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/cohorts.py +0 -14
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/coverage.py +0 -14
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/demand.py +0 -9
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/employees.py +0 -18
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/entrypoints.py +0 -32
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/large.py +0 -16
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/preferences.py +0 -17
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/shifts.py +0 -20
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/skills.py +0 -15
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/time_utils.py +0 -5
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/validation.py +0 -12
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/vocabulary.py +0 -10
- solverforge-0.5.0/examples/solverforge_hospital/src/data/data_seed/witness.py +0 -10
- solverforge-0.5.0/examples/solverforge_hospital/src/data/mod.py +0 -3
- solverforge-0.5.0/examples/solverforge_hospital/src/domain/__init__.py +0 -59
- solverforge-0.5.0/examples/solverforge_hospital/src/domain/care_hub.py +0 -63
- solverforge-0.5.0/examples/solverforge_hospital/src/domain/employee.py +0 -32
- solverforge-0.5.0/examples/solverforge_hospital/src/domain/mod.py +0 -19
- solverforge-0.5.0/examples/solverforge_hospital/src/domain/plan.py +0 -256
- solverforge-0.5.0/examples/solverforge_hospital/src/lib.py +0 -56
- solverforge-0.5.0/examples/solverforge_hospital/src/main.py +0 -22
- solverforge-0.5.0/examples/solverforge_hospital/src/solver/__init__.py +0 -8
- solverforge-0.5.0/examples/solverforge_hospital/src/solver/mod.py +0 -8
- solverforge-0.5.0/examples/solverforge_hospital/src/solver/service/__init__.py +0 -202
- solverforge-0.5.0/examples/solverforge_hospital/src/solver/service/mod.py +0 -8
- solverforge-0.5.0/examples/solverforge_hospital/src/solver/service/payload.py +0 -86
- solverforge-0.5.0/examples/solverforge_hospital/static/app/main.mjs +0 -130
- solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/analysis-modal.mjs +0 -79
- solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/datetime.mjs +0 -60
- solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/employee-view.mjs +0 -95
- solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/grouping.mjs +0 -77
- solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/identity.mjs +0 -33
- solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/location-view.mjs +0 -42
- solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/presentation.mjs +0 -116
- solverforge-0.5.0/examples/solverforge_hospital/static/app/schedule/rail-renderer.mjs +0 -154
- solverforge-0.5.0/examples/solverforge_hospital/static/app/shell/api-guide.mjs +0 -104
- solverforge-0.5.0/examples/solverforge_hospital/static/app/shell/app-shell.mjs +0 -103
- solverforge-0.5.0/examples/solverforge_hospital/static/app/shell/app-state.mjs +0 -7
- solverforge-0.5.0/examples/solverforge_hospital/static/app/shell/config-loader.mjs +0 -63
- solverforge-0.5.0/examples/solverforge_hospital/static/app/shell/data-panel.mjs +0 -22
- solverforge-0.5.0/examples/solverforge_hospital/static/app/shell/solver-controller.mjs +0 -134
- solverforge-0.5.0/examples/solverforge_hospital/static/app/views/registry.mjs +0 -10
- solverforge-0.5.0/examples/solverforge_hospital/static/generated/ui-model.json +0 -41
- solverforge-0.5.0/examples/solverforge_hospital/static/index.html +0 -23
- solverforge-0.5.0/examples/solverforge_hospital/static/sf-config.json +0 -5
- solverforge-0.5.0/examples/vrp_owner_hooks.py +0 -21
- solverforge-0.5.0/python/solverforge/_native.pyi +0 -30
- solverforge-0.5.0/python/solverforge/fields.py +0 -133
- solverforge-0.5.0/python/solverforge/joiner.py +0 -28
- solverforge-0.5.0/scripts/verify_release_artifacts.py +0 -211
- solverforge-0.5.0/scripts/verify_solverforge_release_base.py +0 -109
- solverforge-0.5.0/src/config.rs +0 -58
- solverforge-0.5.0/src/error.rs +0 -9
- solverforge-0.5.0/src/manager/events.rs +0 -154
- solverforge-0.5.0/src/manager/mod.rs +0 -4
- solverforge-0.5.0/src/runtime/distance.rs +0 -17
- solverforge-0.5.0/src/runtime/dynamic_scalar_search.rs +0 -6307
- solverforge-0.5.0/src/runtime/list_slots.rs +0 -28
- solverforge-0.5.0/src/runtime/mod.rs +0 -32
- solverforge-0.5.0/src/runtime/scalar_slots.rs +0 -29
- solverforge-0.5.0/src/runtime/static_fns.rs +0 -21
- solverforge-0.5.0/src/runtime/thread_local.rs +0 -21
- solverforge-0.5.0/src/schema/parse.rs +0 -260
- solverforge-0.5.0/src/schema/types.rs +0 -112
- solverforge-0.5.0/src/schema/validate.rs +0 -38
- solverforge-0.5.0/src/solver/solvable.rs +0 -60
- solverforge-0.5.0/src/state/callback_view.rs +0 -294
- solverforge-0.5.0/src/state/entity_table.rs +0 -51
- solverforge-0.5.0/src/state/marshal.rs +0 -349
- solverforge-0.5.0/src/state/solution.rs +0 -465
- solverforge-0.5.0/src/state/variables.rs +0 -22
- solverforge-0.5.0/tests/python/test_config.py +0 -196
- solverforge-0.5.0/tests/python/test_constraints.py +0 -638
- solverforge-0.5.0/tests/python/test_decorators.py +0 -53
- solverforge-0.5.0/tests/python/test_deliveries_example.py +0 -329
- solverforge-0.5.0/tests/python/test_deliveries_frontend_app.py +0 -255
- solverforge-0.5.0/tests/python/test_examples_import_surface.py +0 -63
- solverforge-0.5.0/tests/python/test_examples_playwright.py +0 -296
- solverforge-0.5.0/tests/python/test_hospital_example.py +0 -169
- solverforge-0.5.0/tests/python/test_hospital_frontend_app.py +0 -365
- solverforge-0.5.0/tests/python/test_list_solving.py +0 -1472
- solverforge-0.5.0/tests/python/test_manager.py +0 -82
- solverforge-0.5.0/tests/python/test_mixed_solving.py +0 -8
- solverforge-0.5.0/tests/python/test_release_metadata.py +0 -197
- solverforge-0.5.0/tests/python/test_scalar_solving.py +0 -1530
- solverforge-0.5.0/tests/python/test_threading.py +0 -11
- solverforge-0.5.0/tests/python/test_tracebacks.py +0 -40
- solverforge-0.5.0/tests/python/test_ui_assets.py +0 -125
- solverforge-0.5.0/tests/rust/constraint_set.rs +0 -38
- solverforge-0.5.0/tests/rust/descriptor.rs +0 -79
- solverforge-0.5.0/tests/rust/manager.rs +0 -6
- solverforge-0.5.0/tests/rust/runtime_slots.rs +0 -108
- solverforge-0.5.0/tests/rust/score.rs +0 -8
- solverforge-0.5.0/tests/rust/state_clone.rs +0 -226
- solverforge-0.5.0/tests/rust.rs +0 -16
- {solverforge-0.5.0 → solverforge-0.6.1}/LICENSE +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/console.py +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/errors.py +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/events.py +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/py.typed +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/score.py +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/typing.py +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/python/solverforge/ui.py +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/rust-toolchain.toml +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/callbacks/call.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/callbacks/mod.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/callbacks/registry.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/callbacks/threading.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/evaluate.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/incremental.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/matches.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/constraints/stream_plan.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/descriptor/extractor.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/descriptor/mod.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/descriptor/variable.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/intern.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/lib.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/proxy/entity.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/proxy/list.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/proxy/mod.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/proxy/solution.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/schema/build.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/schema/python.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/score.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/solver/mod.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/solver/progress.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/solver/result.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/state/clone.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/state/mod.rs +0 -0
- {solverforge-0.5.0 → solverforge-0.6.1}/src/ui.rs +0 -0
- {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.
|
|
610
|
+
version = "0.18.0"
|
|
611
611
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
612
|
-
checksum = "
|
|
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.
|
|
622
|
+
version = "0.18.0"
|
|
623
623
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
624
|
-
checksum = "
|
|
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.
|
|
635
|
+
version = "0.18.0"
|
|
636
636
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
637
|
-
checksum = "
|
|
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.
|
|
647
|
+
version = "0.18.0"
|
|
648
648
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
649
|
-
checksum = "
|
|
649
|
+
checksum = "d922c9a0ee10e0ba68c7c9b98e38d07399820358dc0d10d238a9e6641ef45d27"
|
|
650
650
|
dependencies = [
|
|
651
651
|
"thiserror",
|
|
652
652
|
]
|
|
653
653
|
|
|
654
654
|
[[package]]
|
|
655
655
|
name = "solverforge-scoring"
|
|
656
|
-
version = "0.
|
|
656
|
+
version = "0.18.0"
|
|
657
657
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
658
|
-
checksum = "
|
|
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.
|
|
666
|
+
version = "0.18.0"
|
|
667
667
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
668
|
-
checksum = "
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
35
|
-
solverforge-bridge = { version = "=0.
|
|
36
|
-
solverforge-console = { version = "=0.
|
|
37
|
-
solverforge-core = { version = "=0.
|
|
38
|
-
solverforge-scoring = { version = "=0.
|
|
39
|
-
solverforge-solver = { version = "=0.
|
|
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
|
+
|