API Summary

Quick reference for SolverForge Python API.

Quick reference for commonly used SolverForge APIs.

Domain Decorators

from solverforge_legacy.solver.domain import (
    planning_entity,
    planning_solution,
)
DecoratorPurpose
@planning_entityMark a class as a planning entity
@planning_solutionMark a class as the planning solution

Type Annotations

from solverforge_legacy.solver.domain import (
    PlanningId,
    PlanningVariable,
    PlanningListVariable,
    PlanningEntityCollectionProperty,
    ProblemFactCollectionProperty,
    ValueRangeProvider,
    PlanningScore,
    PlanningPin,
    PlanningPinToIndex,
)
AnnotationUse WithPurpose
PlanningIdEntity fieldUnique identifier
PlanningVariableEntity fieldVariable to optimize
PlanningListVariableEntity fieldOrdered list of entities
PlanningEntityCollectionPropertySolution fieldCollection of entities
ProblemFactCollectionPropertySolution fieldImmutable input data
ValueRangeProviderSolution fieldPossible values for variables
PlanningScoreSolution fieldWhere score is stored
PlanningPinEntity fieldLock entity assignment
PlanningPinToIndexEntity fieldLock list position

Usage Pattern

from typing import Annotated
from dataclasses import dataclass, field

@planning_entity
@dataclass
class Lesson:
    id: Annotated[str, PlanningId]
    timeslot: Annotated[Timeslot | None, PlanningVariable] = field(default=None)

Shadow Variable Annotations

from solverforge_legacy.solver.domain import (
    InverseRelationShadowVariable,
    PreviousElementShadowVariable,
    NextElementShadowVariable,
    CascadingUpdateShadowVariable,
)
AnnotationPurpose
InverseRelationShadowVariableBack-reference to list owner
PreviousElementShadowVariablePrevious element in list
NextElementShadowVariableNext element in list
CascadingUpdateShadowVariableComputed value that cascades

Score Types

from solverforge_legacy.solver.score import (
    SimpleScore,
    HardSoftScore,
    HardMediumSoftScore,
    HardSoftDecimalScore,
)
TypeLevelsExample
SimpleScore1-5
HardSoftScore2-2hard/-15soft
HardMediumSoftScore3-1hard/-3medium/-10soft
HardSoftDecimalScore2 (decimal)-2hard/-15.5soft

Common Operations

score = HardSoftScore.of(-2, -15)
score.hard_score      # -2
score.soft_score      # -15
score.is_feasible     # False (hard_score < 0)

# Constants
HardSoftScore.ZERO
HardSoftScore.ONE_HARD
HardSoftScore.ONE_SOFT

Constraint Streams

from solverforge_legacy.solver.score import (
    constraint_provider,
    ConstraintFactory,
    Constraint,
    Joiners,
    ConstraintCollectors,
)

ConstraintFactory Methods

MethodPurpose
for_each(Class)Start stream with all instances
for_each_unique_pair(Class, *Joiners)All unique pairs
for_each_including_unassigned(Class)Include entities with null variables

Stream Operations

MethodPurpose
.filter(predicate)Filter elements
.join(Class, *Joiners)Join with another class
.if_exists(Class, *Joiners)Keep if matching exists
.if_not_exists(Class, *Joiners)Keep if no matching exists
.group_by(groupKey, collector)Group and aggregate
.flatten_last(mapper)Expand collection
.map(mapper)Transform elements
.complement(Class, filler)Add missing elements

Terminal Operations

MethodPurpose
.penalize(Score)Add penalty
.penalize(Score, weigher)Weighted penalty
.reward(Score)Add reward
.reward(Score, weigher)Weighted reward
.penalize_decimal(Score, weigher)Decimal penalty
.as_constraint(name)Name the constraint

Joiners

from solverforge_legacy.solver.score import Joiners
JoinerPurpose
Joiners.equal(extractor)Match on equality
Joiners.equal(extractorA, extractorB)Match properties
Joiners.less_than(extractorA, extractorB)A < B
Joiners.less_than_or_equal(extractorA, extractorB)A <= B
Joiners.greater_than(extractorA, extractorB)A > B
Joiners.greater_than_or_equal(extractorA, extractorB)A >= B
Joiners.overlapping(startA, endA, startB, endB)Time overlap
Joiners.overlapping(startA, endA)Same start/end extractors
Joiners.filtering(predicate)Custom filter

Collectors

from solverforge_legacy.solver.score import ConstraintCollectors
CollectorResult
count()Number of items
count_distinct(mapper)Distinct count
sum(mapper)Sum of values
min(mapper)Minimum value
max(mapper)Maximum value
average(mapper)Average value
to_list(mapper)Collect to list
to_set(mapper)Collect to set
load_balance(keyMapper, loadMapper)Fairness measure
compose(c1, c2, combiner)Combine collectors

Solver Configuration

from solverforge_legacy.solver.config import (
    SolverConfig,
    ScoreDirectorFactoryConfig,
    TerminationConfig,
    Duration,
)

SolverConfig

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)
    ),
)

TerminationConfig Options

PropertyTypePurpose
spent_limitDurationTime limit
unimproved_spent_limitDurationTime without improvement
best_score_limitstrTarget score
best_score_feasibleboolStop when feasible

Solver API

from solverforge_legacy.solver import (
    SolverFactory,
    SolverManager,
    SolutionManager,
    SolverStatus,
)

SolverFactory

solver_factory = SolverFactory.create(config)
solver = solver_factory.build_solver()
solution = solver.solve(problem)

SolverManager

solver_manager = SolverManager.create(solver_factory)

# Async solving
solver_manager.solve_and_listen(
    problem_id,
    problem_finder=lambda _: problem,
    best_solution_consumer=on_best_solution,
)

# Control
solver_manager.terminate_early(problem_id)
status = solver_manager.get_solver_status(problem_id)
solver_manager.close()

SolverStatus

StatusMeaning
NOT_SOLVINGNot started
SOLVING_ACTIVECurrently solving
SOLVING_SCHEDULEDQueued

SolutionManager

solution_manager = SolutionManager.create(solver_factory)
analysis = solution_manager.analyze(solution)
score = solution_manager.update(solution)

Duration

from solverforge_legacy.solver.config import Duration

Duration(seconds=30)
Duration(minutes=5)
Duration(hours=1)

Event Listeners

from solverforge_legacy.solver import BestSolutionChangedEvent

def on_best_solution(event: BestSolutionChangedEvent):
    print(f"Score: {event.new_best_score}")
    print(f"Time: {event.time_spent}")

Problem Changes

from solverforge_legacy.solver import ProblemChange

class AddLessonChange(ProblemChange):
    def __init__(self, lesson: Lesson):
        self.lesson = lesson

    def do_change(self, solution: Timetable, problem_change_director):
        problem_change_director.add_entity(
            self.lesson,
            lambda l: solution.lessons.append(l)
        )

Import Summary

# Domain modeling
from solverforge_legacy.solver.domain import (
    planning_entity,
    planning_solution,
    PlanningId,
    PlanningVariable,
    PlanningListVariable,
    PlanningEntityCollectionProperty,
    ProblemFactCollectionProperty,
    ValueRangeProvider,
    PlanningScore,
    PlanningPin,
    InverseRelationShadowVariable,
    PreviousElementShadowVariable,
    NextElementShadowVariable,
    CascadingUpdateShadowVariable,
)

# Scores and constraints
from solverforge_legacy.solver.score import (
    constraint_provider,
    ConstraintFactory,
    Constraint,
    Joiners,
    ConstraintCollectors,
    HardSoftScore,
    HardMediumSoftScore,
    SimpleScore,
)

# Solver
from solverforge_legacy.solver import (
    SolverFactory,
    SolverManager,
    SolutionManager,
    SolverStatus,
    BestSolutionChangedEvent,
    ProblemChange,
)

# Configuration
from solverforge_legacy.solver.config import (
    SolverConfig,
    ScoreDirectorFactoryConfig,
    TerminationConfig,
    Duration,
)