Skip to main content

SolverForge (Legacy)

SolverForge

PyPI Python support License

Powered by:

Timefold Logo

Planning optimization made easy: timefold.ai

SolverForge is a 100% Timefold 1.24.0 compatible AI constraint solver you can use to optimize the Vehicle Routing Problem, Employee Rostering, Maintenance Scheduling, Task Assignment, School Timetabling, Cloud Optimization, Conference Scheduling, Job Shop Scheduling and many more planning problems with Python.

Using Timefold Solver in Python used to be significantly slower than using Timefold Solver for Java or Kotlin.

Therefore, the official Timefold Python solver has been discontinued and the Timefold team is focusing on the Java and Kotlin solvers.

SolverForge is committed to three core goals:

  • Performance: We are optimizing quickstart examples and have already achieved significant speedups by removing bottlenecks (for example, pydantic now validates only at the API boundary, not during solving). See our latest benchmark results for reference.

  • Sustained Support: This fork offers ongoing community support—issues and bugs will be addressed — but we will not track every new Timefold release. Our baseline remains version 1.24.0.

  • Next-Generation Architecture: In the meantime, we are developing a fully native Rust backend to deliver a new 100% API-compatible Python solver. This approach eliminates JPype overhead and connects directly to the Timefold JVM via JNI for maximum performance and efficiency.

Get started with SolverForge

  • Clone SolverForge Quickstarts: git clone https://github.com/SolverForge/solverforge-quickstarts.git

  • Navigate to the quickstarts directory and choose a quickstart: cd solverforge-quickstarts/hello-world

Requirements

Build from source

  1. Install the repo
    $ pip install git+https://github.com/SolverForge/solverforge-legacy.git
    

Source code overview

Domain

In Timefold Solver, the domain has three parts:

  • Problem Facts, which do not change.
  • Planning Entities, which have one or more planning variables.
  • Planning Solution, which define the facts and entities of the problem.

Problem Facts

Problem facts can be any Python class, which are used to describe unchanging facts in your problem:

from dataclasses import dataclass
from datetime import time

@dataclass
class Timeslot:
    id: int
    day_of_week: str
    start_time: time
    end_time: time

Planning Entities

To declare Planning Entities, use the @planning_entity decorator along with annotations:

from dataclasses import dataclass, field
from typing import Annotated
from solverforge_legacy.solver.domain import planning_entity, PlanningId, PlanningVariable

@planning_entity
@dataclass
class Lesson:
    id: Annotated[int, PlanningId]
    subject: str
    teacher: str
    student_group: str
    timeslot: Annotated[Timeslot, PlanningVariable] = field(default=None)
    room: Annotated[Room, PlanningVariable] = field(default=None)
  • The PlanningVariable annotation is used to mark what fields the solver is allowed to change.

  • The PlanningId annotation is used to uniquely identify an entity object of a particular class. The same Planning Id can be used on entities of different classes, but the ids of all entities in the same class must be different.

Planning Solution

To declare the Planning Solution, use the @planning_solution decorator:

from dataclasses import dataclass, field
from typing import Annotated
from solverforge_legacy.solver.domain import (planning_solution, ProblemFactCollectionProperty, ValueRangeProvider,
                                    PlanningEntityCollectionProperty, PlanningScore)
from solverforge_legacy.solver.score import HardSoftScore

@planning_solution
@dataclass
class TimeTable:
    timeslots: Annotated[list[Timeslot], ProblemFactCollectionProperty, ValueRangeProvider]
    rooms: Annotated[list[Room], ProblemFactCollectionProperty, ValueRangeProvider]
    lessons: Annotated[list[Lesson], PlanningEntityCollectionProperty]
    score: Annotated[HardSoftScore, PlanningScore] = field(default=None)
  • The ValueRangeProvider annotation is used to denote a field that contains possible planning values for a PlanningVariable.

  • TheProblemFactCollection annotation is used to denote a field that contains problem facts. This allows these facts to be queried in your constraints.

  • The PlanningEntityCollection annotation is used to denote a field that contains planning entities. The planning variables of these entities will be modified during solving.

  • The PlanningScore annotation is used to denote the field that holds the score of the current solution. The solver will set this field during solving.

Constraints

You define your constraints by using the ConstraintFactory:

from domain import Lesson
from solverforge_legacy.solver.score import (Joiners, HardSoftScore, ConstraintFactory,
                                   Constraint, constraint_provider)

@constraint_provider
def define_constraints(constraint_factory: ConstraintFactory) -> list[Constraint]:
    return [
        # Hard constraints
        room_conflict(constraint_factory),
        # Other constraints here...
    ]

def room_conflict(constraint_factory: ConstraintFactory) -> Constraint:
    # A room can accommodate at most one lesson at the same time.
    return (
        constraint_factory.for_each_unique_pair(Lesson,
                # ... in the same timeslot ...
                Joiners.equal(lambda lesson: lesson.timeslot),
                # ... in the same room ...
                Joiners.equal(lambda lesson: lesson.room))
            .penalize(HardSoftScore.ONE_HARD)
            .as_constraint("Room conflict")
    )

Also see Timefold Solver Documentation on Constraint Streams.

Solve

from solverforge_legacy.solver import SolverFactory
from solverforge_legacy.solver.config import SolverConfig, TerminationConfig, ScoreDirectorFactoryConfig, Duration
from constraints import define_constraints
from domain import TimeTable, Lesson, generate_problem

solver_config = SolverConfig(
    solution_class=TimeTable,
    entity_class_list=[Lesson],
    score_director_factory_config=ScoreDirectorFactoryConfig(
        constraint_provider_function=define_constraints
    ),
    termination_config=TerminationConfig(
        spent_limit=Duration(seconds=30)
    )
)

solver = SolverFactory.create(solver_config).build_solver()
solution = solver.solve(generate_problem())

solution will be a TimeTable instance with planning variables set to the final best solution found.

For a full API spec, visit the Timefold Documentation.

Legal notice

SolverForge was forked on 03 August 2025 from Timefold's Python Solver, which was entirely Apache-2.0 licensed (a permissive license).

The original Timefold Python Solver was forked on 20 April 2023 from OptaPlanner and OptaPy.

SolverForge is a derivative work of the Timefold Python Solver and OptaPy, which includes copyrights of the original creators, Timefold AI, Red Hat Inc., affiliates, and contributors, that were all entirely licensed under the Apache-2.0 license. Every source file has been modified.

Release files for solverforge-legacy 1.24.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for solverforge-legacy 1.24.1
File Size Uploaded
solverforge_legacy-1.24.1.tar.gz 395.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for solverforge-legacy 1.24.1
File Interpreter ABI Platform
solverforge_legacy-1.24.1-py3-none-any.whl Python 3 none any Details

Total release size: 22.3 MB

Release files / solverforge_legacy-1.24.1.tar.gz

Download URL solverforge_legacy-1.24.1.tar.gz
Size 395.0 kB
Tags Source
SHA-256 checksum
How to use checksums
e51ad02efc31fdbfcaa684671889ddf2006bd0a81fa322fc20bd97ce92e92ef5
BLAKE2b-256 checksum
How to use checksums
774bec3320660346ffa0a5f0632f27923c235ee6655334e38956c3c1cd2f0942
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.10.19

Release files / solverforge_legacy-1.24.1-py3-none-any.whl

Download URL solverforge_legacy-1.24.1-py3-none-any.whl
Size 21.9 MB
Tags Python 3
SHA-256 checksum
How to use checksums
86ee7b0f852a304f3b9f99178b20d564ac1f8805f4c808139e3d03b63e086c4a
BLAKE2b-256 checksum
How to use checksums
63440db4985faa2f57668389b4a1c5c274d90d456d434a1bb3469f4cd2dd0ec7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.10.19

Release history Release notifications | RSS feed

This release

1.24.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page