Skip to main content

Dynamic Python bindings for the SolverForge constraint solver.

Project description

SolverForge Python

solverforge is the dynamic Python binding package for SolverForge. Python users define models with Python classes, decorators, functions, and lambdas. They do not write Rust and they do not pass JSON to a fixed backend.

The native extension owns the working solution state in Rust so SolverForge can clone, mutate, and snapshot solutions safely. Python callbacks are the single constraint authoring surface.

The package targets CPython 3.14 and Rust 1.95.0. The PyPI package starts at solverforge 0.4.0 for this architecture and intentionally supersedes the older incompatible 0.2.x and 0.3.0 artifacts in the same PyPI namespace. Those older artifacts exposed SolverFactory, PlanningVariable, Java service requirements, and other APIs that are not part of this package.

Installation

python3.14 -m pip install solverforge

The installable wheel contains the core solverforge package and native extension. Source-checkout examples, including the hospital FastAPI app and its static assets, are maintained in this repository rather than installed into the runtime wheel.

Local development is driven by the root Makefile, which creates .venv, installs maturin and developer tools, and builds the PyO3 extension against the current checkout.

from solverforge import (
    ConstraintFactory,
    HardSoftScore,
    Solver,
    constraint_provider,
    planning_entity,
    planning_solution,
    planning_variable,
)

@planning_entity
class Shift:
    nurse = planning_variable(value_range_provider="nurses", allows_unassigned=True)

    def __init__(self, required: bool = True, nurse: int | None = None) -> None:
        self.required = required
        self.nurse = nurse

@constraint_provider
def constraints(factory: ConstraintFactory):
    return [
        factory.for_each(Shift)
        .filter(lambda shift: shift.required and shift.nurse is None)
        .penalize(HardSoftScore.ONE_HARD)
        .named("required shift is unassigned")
    ]

@planning_solution(score=HardSoftScore, constraints=constraints)
class Schedule:
    shifts: list[Shift]

    def __init__(self, shifts: list[Shift], nurses: list[int]) -> None:
        self.shifts = shifts
        self.nurses = nurses
        self.score = None

solution = Solver.solve(Schedule([Shift(), Shift()], [0, 1]))

Development

make develop          # release native extension installed into .venv
make test             # cargo test plus pytest
make lint             # rustfmt check, ruff, mypy, and clippy
make ci-local         # local CI simulation
make pre-release      # ci-local plus release sdist/wheel checks

Run make help for focused targets such as make test-hospital, make test-one TEST=pattern, make hospital-run, and make hospital-solve.

Boundaries

  • No generated Rust.
  • No expression-object DSL.
  • No string-parsed constraints.
  • No private upstream SolverForge modules; bindings use the public dynamic bridge contract.
  • No fixed pre-modeled JSON-only backends.

Current Support

  • Solver.solve(..., config=None) and SolverManager(config=None) load a user-space solver.toml from the current directory when present. Explicit SolverConfig or dict configs are normalized before Rust handoff, and top-level plus phase-level termination fields match upstream SolverForge: seconds_spent_limit, minutes_spent_limit, best_score_limit, step_count_limit, unimproved_step_count_limit, and unimproved_seconds_spent_limit.
  • Synchronous and retained scalar/list construction solves use upstream SolverForge.
  • SolverManager is backed by upstream retained jobs, statuses, events, and snapshots, including pause, resume, cancel, delete, and exact snapshot reads.
  • Supported score families are SoftScore, HardSoftScore, HardSoftDecimalScore, and HardMediumSoftScore.
  • Python callback constraints are evaluated from Rust-owned dynamic state. Supported stream shapes are unary for_each(...).filter(...), binary for_each(...).join(...).filter(...), and grouped-count for_each(...).group_by(...), plus for_each(...).balance(...), with fixed or callback-computed score weights. joiner.equal(...) and joiner.equal_bi(...) preserve Python equality semantics.
  • Dynamic scalar local search is available through upstream-style dynamic change_move_selector, swap_move_selector, nearby_change_move_selector, nearby_swap_move_selector, pillar_change_move_selector, pillar_swap_move_selector, ruin_recreate_move_selector, grouped_scalar_move_selector, conflict_repair_move_selector, and compound_conflict_repair_move_selector phases.
  • Dynamic list local search is available through upstream-style list_change_move_selector, nearby_list_change_move_selector, list_swap_move_selector, nearby_list_swap_move_selector, sublist_change_move_selector, sublist_swap_move_selector, list_reverse_move_selector, k_opt_move_selector, and list_ruin_move_selector phases.
  • limited_neighborhood, union_move_selector, and two-child cartesian_product_move_selector compose supported dynamic scalar and list selectors.
  • examples/solverforge_hospital is a 1:1 Python model of the Rust hospital use case's public dataset/config surface: LARGE has 50 employees, 688 shifts, retained jobs, snapshots, analysis, pause/resume/cancel, and a 30-second hard-feasible terminal solve when installed with the release native extension.
  • Top-level ConstraintFactory.join, group_by, if_exists, if_not_exists, and flattened remain explicit unsupported methods until the native callback stream planner has public bridge support for those top-level semantics. Use stream-level for_each(...).join(...) and for_each(...).group_by(...) for the supported join and grouped-count surfaces.

Documentation Map

  • WIREFRAME.md is the as-built public API, runtime, and hospital UI map.
  • AGENTS.md is the contributor guide and agent scope contract.
  • docs/upstream-contract.md records the public SolverForge bridge assumptions.
  • docs/dynamic-move-parity-plan.md tracks implemented dynamic selector parity.

Rust macro-generated SolverForge models remain the performance ceiling. The Python path preserves the Rust solver engine and Rust-owned state while paying the honest dynamic callback boundary cost.

License

SolverForge Python is licensed under the Apache License, Version 2.0. See LICENSE for the full license text.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

solverforge-0.4.0.tar.gz (756.3 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

solverforge-0.4.0-cp314-cp314-win_amd64.whl (1.6 MB view details)

Uploaded CPython 3.14Windows x86-64

solverforge-0.4.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.7 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ x86-64

solverforge-0.4.0-cp314-cp314-macosx_11_0_arm64.whl (1.5 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

File details

Details for the file solverforge-0.4.0.tar.gz.

File metadata

  • Download URL: solverforge-0.4.0.tar.gz
  • Upload date:
  • Size: 756.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for solverforge-0.4.0.tar.gz
Algorithm Hash digest
SHA256 88f19a1f85e01b1e460c44585d4124fc18d3aed58c2b5ed45104e0b6e2b2d737
MD5 8285ff17f362c54c65fc624df8409987
BLAKE2b-256 4d21beee47335295f7088c6bf463317ee61301524434fc2e825f78fd8a074c96

See more details on using hashes here.

File details

Details for the file solverforge-0.4.0-cp314-cp314-win_amd64.whl.

File metadata

File hashes

Hashes for solverforge-0.4.0-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 86a64d45ab2a25b07618b9aa27e769fe9acd6f7d3d529dcf8caf86c31ab68f02
MD5 32c89b7b8d849b7c51c15eeb23d55451
BLAKE2b-256 1f2dccb9585912ccd67877bde6a668bc2b9c86ea1612f3810698913be03b648c

See more details on using hashes here.

File details

Details for the file solverforge-0.4.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for solverforge-0.4.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 1a5596e912ce68c531e0d6adc60a9d67a90f722b48c5bf61ded3f36f793e0682
MD5 70d899406a1b4dd6ca970f9cbf27889c
BLAKE2b-256 9cede5aad4ce781be2246b6192f67edb2b1dec1d94d66db1e9f62af51543225f

See more details on using hashes here.

File details

Details for the file solverforge-0.4.0-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for solverforge-0.4.0-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 ffcec30b788ea0ac5f39c75fbdd966ff1bce331893b1f511de51b6d539c0ef06
MD5 4bdcecf9bcea82565a19cdde0ef7b6ae
BLAKE2b-256 3edb9c164b7b550d6540ac3ef7871760a15c9a06d83ce45459111c79e176a1ae

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page