Skip to main content

Python bindings for SolverForge constraint solver

Project description

SolverForge Python

Python bindings for the SolverForge constraint solver.

Installation

pip install solverforge

Quick Start

from solverforge import (
    planning_entity, planning_solution, constraint_provider,
    PlanningId, PlanningVariable, PlanningListVariable,
    HardSoftScore, SolverFactory, TerminationConfig,
)

@planning_entity
class Lesson:
    id: str
    subject: str
    timeslot: str | None = None
    room: str | None = None

@planning_solution
class Timetable:
    timeslots: list[str]
    rooms: list[str]
    lessons: list[Lesson]
    score: HardSoftScore | None = None

@constraint_provider
def define_constraints(factory):
    return [
        factory.for_each(Lesson)
            .join(Lesson, equal(lambda l: l.room), equal(lambda l: l.timeslot))
            .filter(lambda l1, l2: l1.id < l2.id)
            .penalize(HardSoftScore.ONE_HARD)
            .as_constraint("Room conflict"),
    ]

# Solve
solver = SolverFactory.create(Timetable, define_constraints)
    .with_termination(TerminationConfig.with_spent_limit("PT30S"))
    .build()
solution = solver.solve(problem)

Features

Domain Modeling

from solverforge import (
    planning_entity, planning_solution,
    PlanningVariable, PlanningListVariable,
    InverseRelationShadow, IndexShadow, PreviousElementShadow, NextElementShadow,
)

# Standard planning variable
@planning_entity
class Shift:
    id: str
    employee: str | None = None  # Assigned by solver

# List planning variable (vehicle routing)
@planning_entity
class Vehicle:
    id: str
    visits: list[str] = []  # List of visit IDs, assigned by solver

# Shadow variables (auto-updated when list changes)
@planning_entity
class Visit:
    id: str
    vehicle: str | None = None      # @InverseRelationShadowVariable
    index: int | None = None        # @IndexShadowVariable
    previous: str | None = None     # @PreviousElementShadowVariable
    next: str | None = None         # @NextElementShadowVariable

Constraint Streams

from solverforge import (
    equal, less_than, greater_than, overlapping, filtering,
    count, sum_, min_, max_, to_list, to_set,
)

@constraint_provider
def constraints(factory):
    return [
        # forEach / forEachIncludingUnassigned / forEachUniquePair
        factory.for_each(Lesson),
        factory.for_each_including_unassigned(Lesson),
        factory.for_each_unique_pair(Lesson, equal(lambda l: l.timeslot)),

        # filter
        factory.for_each(Lesson)
            .filter(lambda l: l.room is not None),

        # join with joiners
        factory.for_each(Lesson)
            .join(Room, equal(lambda l: l.room, lambda r: r.id)),

        # ifExists / ifNotExists
        factory.for_each(Lesson)
            .if_exists(Conflict, equal(lambda l: l.id, lambda c: c.lesson_id)),

        # groupBy with collectors
        factory.for_each(Lesson)
            .group_by(lambda l: l.room, count())
            .filter(lambda room, cnt: cnt > 1)
            .penalize(HardSoftScore.ONE_HARD, lambda room, cnt: cnt - 1),

        # map / expand / flattenLast
        factory.for_each(Vehicle)
            .flatten_last(lambda v: v.visits),
    ]

SolverManager (Multi-Problem Solving)

from solverforge import SolverManager, HttpSolverService

service = HttpSolverService("http://localhost:8080")
manager = SolverManager(Timetable, define_constraints, service)

# Solve multiple problems concurrently
manager.solve("problem-1", problem1)
manager.solve("problem-2", problem2)

# Check solutions
solution = manager.get_best_solution("problem-1")
if solution:
    print(f"Score: {solution.score}")

# Terminate
manager.terminate("problem-1")
manager.terminate_all()

ConstraintVerifier (Testing)

from solverforge import ConstraintVerifier

verifier = ConstraintVerifier.build(Timetable, define_constraints)

# Test specific constraint
verifier.verify_that(lambda f: f.for_each(Lesson)...)
    .given(lesson1, lesson2)
    .penalizes_by(1)

# Test entire solution
verifier.verify_that()
    .given_solution(solution)
    .scores(HardSoftScore.of(-2, -10))

Score Types

from solverforge import (
    SimpleScore, HardSoftScore, HardMediumSoftScore,
    BendableScore, SimpleDecimalScore, HardSoftDecimalScore,
)

score = HardSoftScore.of(-2, -15)
print(f"Hard: {score.hard_score}, Soft: {score.soft_score}")
print(f"Feasible: {score.is_feasible()}")

# Arithmetic
combined = score + HardSoftScore.of(0, -5)

Solver Configuration

from solverforge import (
    SolverFactory, SolverConfig, TerminationConfig,
    EnvironmentMode, MoveThreadCount,
)

solver = SolverFactory.create(Timetable, define_constraints)
    .with_termination(
        TerminationConfig()
            .with_spent_limit("PT5M")
            .with_unimproved_spent_limit("PT1M")
            .with_best_score_feasible(True)
    )
    .with_environment_mode(EnvironmentMode.REPRODUCIBLE)
    .with_random_seed(42)
    .with_move_thread_count(MoveThreadCount.AUTO)
    .build()

Requirements

  • Python 3.13+ (required for modern AST support)
  • Java 21+ (for solver service, auto-started)

License

Apache-2.0

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.3.0.tar.gz (238.0 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.3.0-cp310-abi3-win_amd64.whl (3.2 MB view details)

Uploaded CPython 3.10+Windows x86-64

solverforge-0.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

solverforge-0.3.0-cp310-abi3-macosx_11_0_arm64.whl (3.4 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

File details

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

File metadata

  • Download URL: solverforge-0.3.0.tar.gz
  • Upload date:
  • Size: 238.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for solverforge-0.3.0.tar.gz
Algorithm Hash digest
SHA256 466deb27576a35a8c8bf58a38fb4a93a922f8dc251c2f4cdcdc97b7536c9ea8c
MD5 d9fabc01373da1df77bcb6e042c28858
BLAKE2b-256 990332ccf8ca97f7c2f2885c3943e457405e6a05f77d4c6639680d7d74ce04a5

See more details on using hashes here.

Provenance

The following attestation bundles were made for solverforge-0.3.0.tar.gz:

Publisher: python-publish.yml on SolverForge/solverforge

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file solverforge-0.3.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: solverforge-0.3.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 3.2 MB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for solverforge-0.3.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 50e33400c8a2d52f23f3c16ac58e2328fc87c52a663f4eddf9937406a4f526a9
MD5 7051c3ce6bc1e8cbd7c4d041d1339203
BLAKE2b-256 cca8cb9f1ab9f220b94f5e13ebead92fe300629d69b3c6932cbebb598de3b858

See more details on using hashes here.

Provenance

The following attestation bundles were made for solverforge-0.3.0-cp310-abi3-win_amd64.whl:

Publisher: python-publish.yml on SolverForge/solverforge

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file solverforge-0.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for solverforge-0.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 d333ce93d0f3a8fe8c5539d433acf98b885afac327bca67261afd72a78647e45
MD5 9dfffe2f27d337699b3cff67ff2695a1
BLAKE2b-256 da3cd0c27be62fd6082254800cd9bbfff1680b0ee1cf2fb46ff3ee94ae12d37b

See more details on using hashes here.

Provenance

The following attestation bundles were made for solverforge-0.3.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: python-publish.yml on SolverForge/solverforge

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file solverforge-0.3.0-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for solverforge-0.3.0-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 0b0e1c8acef89d38c91d0fb0c3858cbefd2e1dfb94036285f794f581c5670581
MD5 49e118b8edea45a67c9882434a442bef
BLAKE2b-256 415e14e145765f9109be7ac381038088d9a920917ab0c2956f5a7cf47280c986

See more details on using hashes here.

Provenance

The following attestation bundles were made for solverforge-0.3.0-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: python-publish.yml on SolverForge/solverforge

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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