API Reference

Model

class optalcp.Model(*, name: str | None = None)

Bases: object

Model captures the problem to be solved. It contains variables, constraints and objective function.

To create an optimization model, you must first create a Model object. Then you can use the methods of the Model to create variables (e.g. Model.interval_var()), the objective function (Model.minimize() or Model.maximize()) and constraints (e.g. Model.no_overlap()). Note that a boolean expression becomes a constraint only by passing it to Model.enforce(); otherwise, it is not enforced.

To solve a model, pass it to function Model.solve() or to Solver class.

Available modeling elements

Variables

Interval variables can be created by function Model.interval_var(), integer variables by function Model.int_var().

Basic integer expressions

  • Model.start(): start of an interval variable (optional integer expression).

  • Model.end(): end of an interval variable (optional integer expression).

  • Model.length(): length of an interval variable (optional integer expression).

  • Model.guard(): replaces absent value by a constant.

Integer arithmetics

Use standard arithmetic operators on integer expressions: +, -, unary -, *, //.

Comparison operators for integer expressions

Use standard comparison operators on integer expressions: <, <=, ==, !=, >, >=.

  • Model.identity(): constraints two integer expressions to be equal, including the presence status.

Boolean operators

Functions returning BoolExpr

Basic constraints on interval variables

Disjunction (noOverlap)

Basic cumulative expressions

Combining cumulative expressions

Use standard operators on cumulative expressions: +, -, unary -, and Model.sum().

Constraints on cumulative expressions

Use comparison operators on cumulative expressions: <=, >=.

Objective

Example

Our goal is to schedule a set of tasks such that it is finished as soon as possible (i.e., the makespan is minimized). Each task has a fixed duration, and cannot be interrupted. Moreover, each task needs a certain number of workers to be executed, and the total number of workers is limited. The input data are generated randomly.

import optalcp as cp
import random

# Constants for random problem generation:
nb_tasks = 100
nb_workers = 5
max_duration = 100

# Start by creating the model:
model = cp.Model()

# For each task we will have an interval variable and a cumulative expression:
tasks = []
worker_usage = []

# Loop over the tasks:
for i in range(nb_tasks):
    # Generate random task length:
    task_length = 1 + random.randint(0, max_duration - 2)
    # Create the interval variable for the task:
    task = model.interval_var(name=f"Task{i + 1}", length=task_length)
    # And store it in the array:
    tasks.append(task)
    # Generate random number of workers needed for the task:
    workers_needed = 1 + random.randint(0, nb_workers - 2)
    # Create the pulse that increases the number of workers used during the task:
    worker_usage.append(task.pulse(workers_needed))

# Limit the sum of the pulses to the number of workers available:
model.enforce(model.sum(worker_usage) <= nb_workers)
# From an array of tasks, create an array of their ends:
ends = [t.end() for t in tasks]
# And minimize the maximum of the ends:
model.minimize(model.max(ends))

# Solve the model with the provided parameters:
result = model.solve({
    'timeLimit': 3,  # Stop after 3 seconds
    'nbWorkers': 4,  # Use for CPU threads
})

if result.nb_solutions == 0:
    print("No solution found.")
else:
    solution = result.solution
    # Note that in the preview version of the solver, the variable values in
    # the solution are masked, i.e. they are all *absent* (`None` in Python).
    # Objective value is not masked though.
    print(f"Solution found with makespan {solution.get_objective()}")
    for task in tasks:
        start = solution.get_start(task)
        if start is not None:
            print(f"Task {task.name} starts at {start}")
        else:
            print(f"Task {task.name} is absent (not scheduled).")
property name: str | None

The name of the model.

The name is optional and primarily useful for distinguishing between different models during debugging and benchmarking. When set, it helps identify the model in logs and benchmark results.

The name can be set either in the constructor or by assigning to this property. Assigning overwrites any name that was previously set.

import optalcp as cp

# Set name in constructor
model = cp.Model(name="MySchedulingProblem")
print(model.name)  # "MySchedulingProblem"

# Or set name later
model = cp.Model()
model.name = "JobShop"
print(model.name)  # "JobShop"

# Clear the name
model.name = None
get_interval_vars() list[IntervalVar]

Returns a list of all interval variables in the model.

Return type:

list[IntervalVar]

Returns:

A list of all interval variables in the model

Details

Returns a copy of the list containing all interval variables that have been created in this model using Model.interval_var().

Example

import optalcp as cp

model = cp.Model()
task1 = model.interval_var(length=10, name="task1")
task2 = model.interval_var(length=20, name="task2")

intervals = model.get_interval_vars()
print(len(intervals))  # 2
for iv in intervals:
    print(iv.name)  # "task1", "task2"
get_int_vars() list[IntVar]

Returns a list of all integer variables in the model.

Return type:

list[IntVar]

Returns:

A list of all integer variables in the model

Details

Returns a copy of the list containing all integer variables that have been created in this model using Model.int_var().

Example

import optalcp as cp

model = cp.Model()
x = model.int_var(min=0, max=10, name="x")
y = model.int_var(min=0, max=100, name="y")

int_vars = model.get_int_vars()
print(len(int_vars))  # 2
for iv in int_vars:
    print(iv.name)  # "x", "y"
get_bool_vars() list[BoolVar]

Returns a list of all boolean variables in the model.

Return type:

list[BoolVar]

Returns:

A list of all boolean variables in the model

Details

Returns a copy of the list containing all boolean variables that have been created in this model using Model.bool_var().

Example

import optalcp as cp

model = cp.Model()
use_machine_a = model.bool_var(name="use_machine_a")
use_machine_b = model.bool_var(name="use_machine_b")

bool_vars = model.get_bool_vars()
print(len(bool_vars))  # 2
for bv in bool_vars:
    print(bv.name)  # "use_machine_a", "use_machine_b"
interval_var(start: int | tuple[int | None, int | None] | None = None, end: int | tuple[int | None, int | None] | None = None, length: int | tuple[int | None, int | None] | None = None, optional: bool = False, name: str | None = None) IntervalVar

Creates a new interval variable and adds it to the model.

Parameters:
  • start (int | tuple[int | None, int | None] | None) – Fixed start time or range as (min, max). Use None for either bound to keep the default. For example, (None, 100) sets only startMax.

  • end (int | tuple[int | None, int | None] | None) – Fixed end time or range as (min, max). Use None for either bound to keep the default. For example, (None, 100) sets only endMax.

  • length (int | tuple[int | None, int | None] | None) – Fixed length or range as (min, max). Use None for either bound to keep the default. For example, (5, None) sets only lengthMin.

  • optional (bool) – If True, the interval can be absent in a solution. The default is False.

  • name (str | None) – Optional name for debugging purposes.

Return type:

IntervalVar

Returns:

The created interval variable.

Details

An interval variable represents an unknown interval (a task, operation, action) that the solver assigns a value in such a way as to satisfy all constraints. An interval variable has a start, end, and length. In a solution, start <= end and length = end - start.

The interval variable can be optional. In this case, its value in a solution could be absent, meaning that the task/operation is not performed.

Parameters start, end, and length can be either an integer or a tuple of two integers. If an integer is given, it represents a fixed value. If a tuple is given, it represents a range of possible values (min, max). Either element of the tuple can be None to use the default bound. The default range for start, end and length is 0 to IntervalMax.

Example

import optalcp as cp

model = cp.Model()

# Create an interval variable with a fixed start but unknown length:
x = model.interval_var(start=0, length=(10, 20), name="x")

# Create an interval variable with start and end ranges:
y = model.interval_var(start=(0, 5), end=(10, 15), name="y")

# Create an optional interval variable with length range 5..10:
z = model.interval_var(length=(5, 10), optional=True, name="z")

# Set only startMax (startMin remains the default):
w = model.interval_var(start=(None, 100), name="w")

See also

int_var(min: int | None = None, max: int | None = None, optional: bool = False, name: str | None = None) IntVar

Creates a new integer variable and adds it to the model.

Parameters:
  • min (int | None) – Minimum value for the variable. Default is 0.

  • max (int | None) – Maximum value for the variable. Default is IntVarMax.

  • optional (bool) – If True, the variable can be absent in a solution. The default is False.

  • name (str | None) – Optional name for debugging purposes.

Return type:

IntVar

Returns:

The created integer variable.

Details

An integer variable represents an unknown value the solver must find. The variable can be optional. In this case, its value in a solution could be absent, meaning that the solution does not use the variable at all.

The default domain is 0 to IntVarMax. If min or max is not specified (or None), the default value is used.

Example

import optalcp as cp
model = cp.Model()

# Create an integer variable with possible values 1..10:
x = model.int_var(min=1, max=10, name="x")

# Create an optional integer variable with possible values 5..IntVarMax:
y = model.int_var(min=5, optional=True, name="y")

# Create a non-negative integer variable:
z = model.int_var(max=100, name="z")
bool_var(optional: bool = False, name: str | None = None) BoolVar

Creates a new boolean variable and adds it to the model.

Parameters:
  • optional (bool) – If True, the variable can be absent in a solution. The default is False.

  • name (str | None) – Optional name for the variable (useful for debugging)

Return type:

BoolVar

Returns:

The created boolean variable.

Details

A boolean variable represents an unknown truth value (True or False) that the solver must find. Boolean variables are useful for modeling decisions, choices, or logical conditions in your problem.

By default, a boolean variable must be assigned a value (True or False) in every solution. When optional=True, the variable can also be absent, meaning the solution does not use the variable at all. This is useful when the variable represents a decision that may not apply in all scenarios.

Boolean variables support logical operators:

  • ~x for logical NOT

  • x | y for logical OR

  • x & y for logical AND

Example

Create boolean variables to model decisions:

import optalcp as cp

model = cp.Model()
use_machine_a = model.bool_var(name="use_machine_a")
use_machine_b = model.bool_var(name="use_machine_b")

# Constraint: must use at least one machine
model.enforce(use_machine_a | use_machine_b)

# Constraint: cannot use both machines
model.enforce(~(use_machine_a & use_machine_b))

See also

sequence_var(intervals: Iterable[IntervalVar], types: Iterable[int] | None = None, name: str | None = None) SequenceVar

Creates a sequence variable from the provided set of interval variables.

Parameters:
  • intervals (Iterable[IntervalVar]) – Interval variables that will form the sequence in the solution

  • types (Iterable[int] | None) – Types of the intervals, used in particular for transition times

  • name (str | None) – Name assigned to the sequence variable

Return type:

SequenceVar

Returns:

The created sequence variable

Details

Sequence variable is used together with SequenceVar.no_overlap() constraint to model a set of intervals that cannot overlap and so they form a sequence in the solution. Sequence variable allows us to constrain the sequence further. For example, by specifying sequence-dependent minimum transition times.

Types can be used to mark intervals with similar properties. In particular, they behave similarly in terms of transition times. Interval variable intervals[0] will have type type[0], intervals[1] will have type type[1] and so on.

If types are not specified then intervals[0] will have type 0, intervals[1] will have type 1, and so on.

The length of the array types must be the same as the length of the array intervals. Types should be integer numbers in the range 0 to n-1 where n is the number of types.

See also

no_overlap(intervals: Iterable[IntervalVar] | SequenceVar, transitions: Iterable[Iterable[int]] | None = None) Constraint

Constrain a set of interval variables not to overlap.

Parameters:
  • intervals (Iterable[IntervalVar] | SequenceVar) – An array of interval variables or a sequence variable to constrain

  • transitions (Iterable[Iterable[int]] | None) – A 2D square array of minimum transition times between the intervals

Return type:

Constraint

Returns:

The no-overlap constraint.

Details

This function constrains a set of interval variables so they do not overlap. That is, for each pair of interval variables x and y, one of the following must hold:

1. Interval variable x or y is absent. In this case, the absent interval is not scheduled (the task is not performed), so it cannot overlap with any other interval. Only optional interval variables can be absent. 2. Interval variable x is before y, that is, x.end() is less than or equal to y.start(). 3. The interval variable y is before x. That is, y.end() is less than or equal to x.start().

The function can also take a square array transitions of minimum transition times between the intervals. The transition time is the time that must elapse between the end of the first interval and the start of the second interval. The transition time cannot be negative. When transition times are specified, the above conditions 2 and 3 are modified as follows:

  1. x.end() + transitions[i][j] is less than or equal to y.start().

  2. y.end() + transitions[j][i] is less than or equal to x.start().

Where i and j are types of x and y. When an array of intervals is passed, the type of each interval is its index in the array.

Note that minimum transition times are enforced between all pairs of intervals, not only between direct neighbors.

Instead of an array of interval variables, a SequenceVar can be passed. See Model.sequence_var() for how to assign types to intervals in a sequence.

Example

The following example does not use transition times. For example with transition times see SequenceVar.no_overlap().

Let’s consider a set of tasks that must be performed by a single machine. The machine can handle only one task at a time. Each task is characterized by its length and a deadline. The goal is to schedule the tasks on the machine so that the number of missed deadlines is minimized.

tasks = [
    {"length": 10, "deadline": 70},
    {"length": 20, "deadline": 50},
    {"length": 15, "deadline": 50},
    {"length": 30, "deadline": 100},
    {"length": 20, "deadline": 120},
    {"length": 25, "deadline": 90},
    {"length": 30, "deadline": 80},
    {"length": 10, "deadline": 40},
    {"length": 20, "deadline": 60},
    {"length": 25, "deadline": 150},
]

model = cp.Model()

# An interval variable for each task:
task_vars = []
# A boolean expression that is true if the task is late:
is_late = []

for i, task in enumerate(tasks):
    task_var = model.interval_var(name=f"Task{i}", length=task["length"])
    task_vars.append(task_var)
    is_late.append(task_var.end() >= task["deadline"])

# Tasks cannot overlap:
model.no_overlap(task_vars)
# Minimize the number of late tasks:
model.minimize(model.sum(is_late))

result = model.solve()

See also

step_function(values: Iterable[tuple[int, int]]) IntStepFunction

Creates a new integer step function.

Parameters:

values (Iterable[tuple[int, int]]) – An array of points defining the step function in the form [[x0, y0], [x1, y1], …, [xn, yn]], where xi and yi are integers. The array must be sorted by xi

Return type:

IntStepFunction

Returns:

The created step function

Details

Integer step function is a piecewise constant function defined on integer values in range IntVarMin to IntVarMax. The function is defined as follows:

  • \(f(x) = 0\) for \(x < x_0\),

  • \(f(x) = y_i\) for \(x_i \leq x < x_{i+1}\)

  • \(f(x) = y_n\) for \(x \geq x_n\).

Step functions can be used in the following ways:

enforce(constraint: Constraint | BoolExpr | bool | Iterable[Constraint | BoolExpr | bool]) None

Enforces a boolean expression as a constraint in the model.

Parameters:

constraint (Constraint | BoolExpr | bool | Iterable[Constraint | BoolExpr | bool]) – The constraint, boolean expression, or iterable of these to enforce in the model

Details

This is the primary method for enforcing boolean expressions as constraints in the model.

A constraint is satisfied if it is not False. In other words, a constraint is satisfied if it is True or absent.

A boolean expression that is not enforced as a constraint can have arbitrary value in a solution (True, False, or absent). Once enforced as a constraint, it can only be True or absent in the solution.

Note: Constraint objects are automatically registered when created. Passing them to enforce() is accepted but does nothing. For cumulative constraints (cumul <= capacity, cumul >= min_level), using enforce() is recommended for code clarity even though it’s not required.

Accepted argument types

The enforce method accepts several types of arguments:

  1. BoolExpr objects: Boolean expressions created from comparisons (e.g., x <= 5, a == b) or logical operations (e.g., a & b, ~c).

  2. bool values: Python boolean constants True or False.

  3. Constraint objects: Accepted but does nothing (constraints auto-register). Use with cumulative constraints for clarity: model.enforce(cumul <= capacity).

  4. Iterables: Lists, tuples, or generators of the above types.

Example

Basic usage with a boolean expression:

import optalcp as cp

model = cp.Model()
x = model.interval_var(length=10, name="x")

# Enforce boolean expressions as constraints
model.enforce(x.start() >= 0)
model.enforce(x.end() <= 100)

Example

Enforcing multiple constraints at once using an iterable:

model = cp.Model()
tasks = [model.interval_var(length=10, name=f"task_{i}") for i in range(5)]

# Enforce multiple constraints at once
constraints = [task.start() >= 0 for task in tasks]
model.enforce(constraints)

# Or using a generator expression
model.enforce(task.end() <= 100 for task in tasks)

Example

Enforcing multiple boolean expressions:

model = cp.Model()
x = model.int_var(0, 100, name="x")
y = model.int_var(0, 100, name="y")

# Enforce various boolean expressions
model.enforce([
    x + y <= 50,           # From comparison
    x >= 10,               # From comparison
    True,                  # Trivially satisfied constraint
])

See also

minimize(expr: IntExpr | int) Objective

Creates a minimization objective for the provided expression.

Parameters:

expr (IntExpr | int) – The expression to minimize

Return type:

Objective

Returns:

An Objective that minimizes the expression.

Details

Creates an Objective to minimize the given expression. A model can have at most one objective. New objective replaces the old one.

Equivalent of function IntExpr.minimize().

Example

In the following model, we search for a solution that minimizes the maximum end of the two intervals x and y:

import optalcp as cp

model = cp.Model()
x = model.interval_var(length=10, name="x")
y = model.interval_var(length=20, name="y")
model.minimize(model.max2(x.end(), y.end()))
result = model.solve()

See also

maximize(expr: IntExpr | int) Objective

Creates a maximization objective for the provided expression.

Parameters:

expr (IntExpr | int) – The expression to maximize

Return type:

Objective

Returns:

An Objective that maximizes the expression.

Details

Creates an Objective to maximize the given expression. A model can have at most one objective. New objective replaces the old one.

Equivalent of function IntExpr.maximize().

Example

In the following model, we search for a solution that maximizes the length of the interval variable x:

import optalcp as cp

model = cp.Model()
x = model.interval_var(length=(10, 20), name="x")
model.maximize(x.length())
result = model.solve()

See also

sum(args: Iterable[IntExpr | int]) IntExpr
sum(args: Iterable[CumulExpr]) CumulExpr

Overload 1: (args: Iterable[IntExpr | int]) -> IntExpr

Creates an integer expression for the sum of the arguments.

Parameters:

args (Iterable[IntExpr | int]) – Array of integer expressions to sum.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

Absent arguments are ignored (treated as zeros). Therefore, the resulting expression is never absent.

Note that the binary operator + handles absent values differently. For example, when x is absent then:

  • x + 3 is absent.

  • model.sum([x, 3]) is 3.

Example

Let’s consider a set of optional tasks. Due to limited resources and time, only some of them can be executed. Every task has a profit, and we want to maximize the total profit from the executed tasks.

import optalcp as cp

# Lengths and profits of the tasks:
lengths = [10, 20, 15, 30, 20, 25, 30, 10, 20, 25]
profits = [ 5,  6,  7,  8,  9, 10, 11, 12, 13, 14]

model = cp.Model()
tasks: list[cp.IntervalVar] = []
# Profits of individual tasks. The value will be zero if the task is not executed.
task_profits: list[cp.IntExpr] = []

for i in range(len(lengths)):
  # All tasks must finish before time 100:
  task = model.interval_var(name=f"Task{i}", optional=True, length=lengths[i], end=(None, 100))
  tasks.append(task)
  task_profits.append(task.presence() * profits[i])

model.maximize(model.sum(task_profits))
# Tasks cannot overlap:
model.no_overlap(tasks)

result = model.solve({'searchType': 'FDS'})

Overload 2: (args: Iterable[CumulExpr]) -> CumulExpr

Sum of cumulative expressions.

param args:

Array of cumulative expressions to sum.

type args:

Iterable[CumulExpr]

rtype:

CumulExpr

returns:

The resulting cumulative expression

Details

Computes the sum of cumulative functions. The sum can be used, e.g., to combine contributions of individual tasks to total resource consumption.

Limitation: Currently, pulse-based and step-based cumulative expressions cannot be mixed. All expressions in the sum must be either pulse-based or step-based.

presence(arg: IntervalVar | IntExpr | int) BoolExpr

Creates a boolean expression that is true if the given argument is present in the solution.

Parameters:

arg (IntervalVar | IntExpr | int) – The argument to check for presence in the solution

Return type:

BoolExpr

Returns:

A boolean expression that is true if the argument is present in the solution.

Details

The value of the expression remains unknown until a solution is found. The expression can be used in a constraint to restrict possible solutions.

The function is equivalent to IntervalVar.presence() and IntExpr.presence().

Example

In the following example, interval variables x and y must have the same presence status. I.e. they must either be both present or both absent.

import optalcp as cp

model = cp.Model()

x = model.interval_var(name="x", optional=True, length=10, start=[0, 100])
y = model.interval_var(name="y", optional=True, length=10, start=[0, 100])
model.enforce(model.presence(x) == model.presence(y))
Simple constraints over presence

The solver treats binary constraints over presence in a special way: it uses them to better propagate other constraints over the same pairs of variables. Let’s extend the previous example by a constraint that x must end before y starts:

import optalcp as cp

x = model.interval_var(name="x", optional=True, length=10, start=(0, 100))
y = model.interval_var(name="y", optional=True, length=10, start=(0, 100))
model.enforce(model.presence(x) == model.presence(y))
# x.end <= y.start:
precedence = x.end() <= y.start()
model.enforce(precedence)

In this example, the solver sees (propagates) that the minimum start time of y is 10 and maximum end time of x is 90. Without the constraint over presence, the solver could not propagate that because one of the intervals can be absent and the other one present (and so the value of precedence would be absent and the constraint would be satisfied).

To achieve good propagation, it is recommended to use binary constraints over presence when possible. For example, multiple binary constraints can be used instead of a single complicated constraint.

solve(parameters: Parameters | None = None, warm_start: Solution | None = None) SolveResult

Solves the model and returns the result.

Parameters:
  • parameters (Parameters | None) – The parameters for solving

  • warm_start (Solution | None) – The solution to start with

Return type:

SolveResult

Returns:

The result of the solve.

Details

Solves the model using the OptalCP solver and returns the result. This is the main entry point for solving constraint programming models.

The solver searches for solutions that satisfy all constraints in the model. If an objective was specified (using Model.minimize() or Model.maximize()), the solver searches for optimal or near-optimal solutions within the given time limit.

The returned SolveResult contains:

  • solution - The best solution found, or None if no solution was found. Use this to query variable values via methods like get_start(), get_end(), and get_value().

  • objective - The objective value of the best solution (if an objective was specified).

  • nb_solutions - The total number of solutions found during the search.

  • proof - Whether the solver proved optimality or infeasibility.

  • duration - The total time spent solving.

  • Statistics like nb_branches, nb_fails, and nb_restarts.

When an error occurs (e.g., invalid model, solver not found), the function raises an exception.

Parameters

Solver behavior can be controlled via the parameters argument. Common parameters include:

  • timeLimit - Maximum solving time in seconds.

  • solutionLimit - Stop after finding this many solutions.

  • nbWorkers - Number of parallel threads to use.

  • searchType - Search strategy (“LNS”, “FDS”, etc.).

See Parameters for the complete list.

Warm start

If the warm_start parameter is specified, the solver will start with the given solution. The solution must be compatible with the model; otherwise, an error will be raised. The solver will take advantage of the solution to speed up the search: it will search only for better solutions (if it is a minimization or maximization problem). The solver may also try to improve the provided solution by Large Neighborhood Search.

Advanced usage

This is a simple blocking function for basic usage. For advanced features like event callbacks, progress monitoring, or async support, use the Solver class instead.

This method works seamlessly in both regular Python scripts and Jupyter notebooks. In Jupyter (where an event loop is already running), it automatically handles nested event loops.

import optalcp as cp

model = cp.Model()
x = model.interval_var(length=10, name="task_x")
y = model.interval_var(length=20, name="task_y")
x.end_before_start(y)
model.minimize(y.end())

# Basic solve
result = model.solve()
print(f"Objective: {result.objective}")

# Solve with parameters
params: cp.Parameters = {'timeLimit': 60, 'searchType': 'LNS'}
result = model.solve(params)

# Solve with warm start
if result.solution:
    result2 = model.solve(params, warm_start=result.solution)

See also

to_json(parameters: Parameters | None = None, warm_start: Solution | None = None) str

Exports the model to JSON format.

Parameters:
  • parameters (Parameters | None) – Optional solver parameters to include

  • warm_start (Solution | None) – Optional initial solution to include

Return type:

str

Returns:

A string containing the model in JSON format.

Details

The result can be stored in a file for later use. The model can be converted back from JSON format using Model.from_json().

import optalcp as cp

model = cp.Model()
x = model.interval_var(length=10, name="task_x")
y = model.interval_var(length=20, name="task_y")
x.end_before_start(y)
model.minimize(y.end())

# Export to JSON
json_str = model.to_json()

# Save to file
with open("model.json", "w") as f:
    f.write(json_str)

# Later, load from JSON
model2, params2, warm_start2 = cp.Model.from_json(json_str)

See also

to_text(parameters: Parameters | None = None, warm_start: Solution | None = None) str

Converts the model to text format similar to IBM CP Optimizer file format.

Parameters:
  • parameters (Parameters | None) – Optional solver parameters (mostly unused)

  • warm_start (Solution | None) – Optional initial solution to include

Return type:

str

Returns:

Text representation of the model.

Details

The output is human-readable and can be stored in a file. Unlike JSON format, there is no way to convert the text format back into a Model.

The result is so similar to the file format used by IBM CP Optimizer that, under some circumstances, the result can be used as an input file for CP Optimizer. However, some differences between OptalCP and CP Optimizer make it impossible to guarantee the result is always valid for CP Optimizer.

Known issues:

  • OptalCP supports optional integer expressions, while CP Optimizer does not. If the model contains optional integer expressions, the result will not be valid for CP Optimizer or may be badly interpreted. For example, to get a valid CP Optimizer file, don’t use interval.start(), use interval.start_or(default) instead.

  • For the same reason, prefer precedence constraints such as end_before_start() over model.enforce(x.end() <= y.start()).

  • Negative heights in cumulative expressions (e.g., in step_at_start()) are not supported by CP Optimizer.

import optalcp as cp

model = cp.Model()
x = model.interval_var(length=10, name="task_x")
y = model.interval_var(length=20, name="task_y")
x.end_before_start(y)
model.minimize(y.end())

# Convert to text format
text = model.to_txt()
print(text)

# Save to file
with open("model.txt", "w") as f:
    f.write(text)

See also

to_js(parameters: Parameters | None = None, warm_start: Solution | None = None) str

Converts the model to equivalent JavaScript code.

Parameters:
  • parameters (Parameters | None) – Optional solver parameters (included in generated code)

  • warm_start (Solution | None) – Optional initial solution to include

Return type:

str

Returns:

JavaScript code representing the model.

Details

The output is human-readable, executable with Node.js, and can be stored in a file. It is meant as a way to export a model to a format that is executable, human-readable, editable, and independent of other libraries.

This feature is experimental and the result is not guaranteed to be valid in all cases.

import optalcp as cp

model = cp.Model()
x = model.interval_var(length=10, name="task_x")
y = model.interval_var(length=20, name="task_y")
x.end_before_start(y)
model.minimize(y.end())

# Convert to JavaScript code
js_code = model.to_js()
print(js_code)

# Save to file
with open("model.js", "w") as f:
    f.write(js_code)

See also

classmethod from_json(json_str: str) tuple[Model, Parameters | None, Solution | None]

Creates a model from JSON format.

Parameters:

json_str (str) – A string containing the model in JSON format

Return type:

tuple[Model, Parameters | None, Solution | None]

Returns:

A tuple containing the model, optional parameters, and optional warm start solution.

Details

Creates a new Model instance from a JSON string that was previously exported using Model.to_json().

The method returns a tuple with three elements: 1. The reconstructed Model 2. Parameters (if they were included in the JSON), or None 3. Warm start Solution (if it was included in the JSON), or None

Variables in the new model can be accessed using methods like Model.get_interval_vars(), Model.get_int_vars(), etc.

import optalcp as cp

# Create and export a model
model = cp.Model()
x = model.interval_var(length=10, name="task_x")
model.minimize(x.end())

params: cp.Parameters = {'timeLimit': 60}
json_str = model.to_json(params)

# Save to file
with open("model.json", "w") as f:
    f.write(json_str)

# Later, load from file
with open("model.json", "r") as f:
    json_str = f.read()

# Restore model, parameters, and warm start
model2, params2, warm_start2 = cp.Model.from_json(json_str)

# Access variables
interval_vars = model2.get_interval_vars()
print(f"Loaded model with {len(interval_vars)} interval variables")

# Solve with restored parameters
if params2:
    result = model2.solve(params2)
else:
    result = model2.solve()

See also

not_(arg: BoolExpr | bool) BoolExpr

Negation of the boolean expression arg.

Parameters:

arg (BoolExpr | bool) – The boolean expression to negate.

Return type:

BoolExpr

Returns:

The resulting Boolean expression

Details

If the argument has value absent then the resulting expression has also value absent.

Same as BoolExpr.not_().

or_(lhs: BoolExpr | bool, rhs: BoolExpr | bool) BoolExpr

Logical _OR_ of boolean expressions lhs and rhs.

Parameters:
Return type:

BoolExpr

Returns:

The resulting Boolean expression

Details

If one of the arguments has value absent, then the resulting expression also has value absent.

Same as BoolExpr.or_().

and_(lhs: BoolExpr | bool, rhs: BoolExpr | bool) BoolExpr

Logical _AND_ of boolean expressions lhs and rhs.

Parameters:
Return type:

BoolExpr

Returns:

The resulting Boolean expression

Details

If one of the arguments has value absent, then the resulting expression also has value absent.

Same as BoolExpr.and_().

implies(lhs: BoolExpr | bool, rhs: BoolExpr | bool) BoolExpr

Logical implication of two boolean expressions, that is lhs implies rhs.

Parameters:
Return type:

BoolExpr

Returns:

The resulting Boolean expression

Details

If one of the arguments has value absent, then the resulting expression also has value absent.

Same as BoolExpr.implies().

guard(arg: IntExpr | int, absent_value: int = 0) IntExpr

Creates an expression that replaces value absent by a constant.

Parameters:
  • arg (IntExpr | int) – The integer expression to guard.

  • absent_value (int) – The value to use when the expression is absent.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

The resulting expression is:

  • equal to arg if arg is present

  • and equal to absent_value otherwise (i.e. when arg is absent).

The default value of absent_value is 0.

The resulting expression is never absent.

Same as IntExpr.guard().

identity(lhs: IntExpr | int, rhs: IntExpr | int) Constraint

Constrains lhs and rhs to be identical, including their presence status.

Parameters:
  • lhs (IntExpr | int) – The first integer expression.

  • rhs (IntExpr | int) – The second integer expression.

Return type:

Constraint

Returns:

The identity constraint.

Details

Identity is different than equality. For example, if x is absent, then eq(x, 0) is absent, but identity(x, 0) is False.

Same as IntExpr.identity().

in_range(arg: IntExpr | int, lb: int, ub: int) BoolExpr

Creates Boolean expression lb &le; arg &le; ub.

Parameters:
  • arg (IntExpr | int) – The integer expression to check.

  • lb (int) – The lower bound of the range.

  • ub (int) – The upper bound of the range.

Return type:

BoolExpr

Returns:

The resulting Boolean expression

Details

If arg has value absent then the resulting expression has also value absent.

Use Model.enforce() to add this expression as a constraint to the model.

Same as IntExpr.in_range().

abs(arg: IntExpr | int) IntExpr

Creates an integer expression which is absolute value of arg.

Parameters:

arg (IntExpr | int) – The integer expression.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

If arg has value absent then the resulting expression has also value absent.

Same as IntExpr.abs().

min2(lhs: IntExpr | int, rhs: IntExpr | int) IntExpr

Creates an integer expression which is the minimum of lhs and rhs.

Parameters:
  • lhs (IntExpr | int) – The first integer expression.

  • rhs (IntExpr | int) – The second integer expression.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

If one of the arguments has value absent, then the resulting expression also has value absent.

Same as IntExpr.min2(). See Model.min() for n-ary minimum.

max2(lhs: IntExpr | int, rhs: IntExpr | int) IntExpr

Creates an integer expression which is the maximum of lhs and rhs.

Parameters:
  • lhs (IntExpr | int) – The first integer expression.

  • rhs (IntExpr | int) – The second integer expression.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

If one of the arguments has value absent, then the resulting expression also has value absent.

Same as IntExpr.max2(). See Model.max() for n-ary maximum.

max(args: Iterable[IntExpr | int]) IntExpr

Creates an integer expression for the maximum of the arguments.

Parameters:

args (Iterable[IntExpr | int]) – Array of integer expressions to compute maximum of.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

Absent arguments are ignored as if they were not specified in the input array args. Maximum of an empty set (i.e. max([]) is absent. The maximum is absent also if all arguments are absent.

Note that binary function Model.max2() handles absent values differently. For example, when x is absent then:

  • max2(x, 5) is absent.

  • max([x, 5]) is 5.

  • max([x]) is absent.

Example

A common use case is to compute makespan of a set of tasks, i.e. the time when the last task finishes. In the following example, we minimize the makespan of a set of tasks (other parts of the model are omitted).

import optalcp as cp

model = cp.Model()
# Create some tasks (lengths would typically come from problem data):
tasks = [model.interval_var(length=10, name=f"task_{i}") for i in range(5)]
# Create an array of end times of the tasks:
end_times = [task.end() for task in tasks]
makespan = model.max(end_times)
model.minimize(makespan)

Notice that when a task is absent (not executed), then its end time is absent. And therefore, the absent task is not included in the maximum.

See also

  • Binary Model.max2().

  • Function Model.span() constraints interval variable to start and end at minimum and maximum of the given set of intervals.

min(args: Iterable[IntExpr | int]) IntExpr

Creates an integer expression for the minimum of the arguments.

Parameters:

args (Iterable[IntExpr | int]) – Array of integer expressions to compute minimum of.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

Absent arguments are ignored as if they were not specified in the input array args. Minimum of an empty set (i.e. min([])) is absent. The minimum is absent also if all arguments are absent.

Note that binary function Model.min2() handles absent values differently. For example, when x is absent then:

  • min2(x, 5) is absent.

  • min([x, 5]) is 5.

  • min([x]) is absent.

Example

In the following example, we compute the time when the first task of tasks starts, i.e. the minimum of the starting times.

import optalcp as cp

model = cp.Model()
# Create some tasks (lengths would typically come from problem data):
tasks = [model.interval_var(length=10, name=f"task_{i}") for i in range(5)]
# Create an array of start times of the tasks:
start_times = [task.start() for task in tasks]
first_start_time = model.min(start_times)

Notice that when a task is absent (not executed), its end time is absent. And therefore, the absent task is not included in the minimum.

See also

  • Binary Model.min2().

  • Function Model.span() constraints interval variable to start and end at minimum and maximum of the given set of intervals.

lex_le(lhs: Iterable[IntExpr | int], rhs: Iterable[IntExpr | int]) Constraint

Lexicographic less than or equal constraint: lhsrhs.

Parameters:
  • lhs (Iterable[IntExpr | int]) – The left-hand side array of integer expressions.

  • rhs (Iterable[IntExpr | int]) – The right-hand side array of integer expressions.

Return type:

Constraint

Returns:

The lexicographic constraint.

Details

Constrains lhs to be lexicographically less than or equal rhs.

Both arrays must have the same length, and the length must be at least 1.

Lexicographic ordering compares arrays element by element from the first position. The comparison lhsrhs holds if and only if:

  • all elements are equal (lhs[i] == rhs[i] for all i), or

  • there exists a position k where lhs[k] < rhs[k] and all preceding elements are equal (lhs[i] == rhs[i] for all i < k)

Lexicographic constraints are useful for symmetry breaking. For example, when you have multiple equivalent solutions that differ only in the ordering of symmetric variables, adding a lexicographic constraint can eliminate redundant solutions.

Example

model = cp.Model()

# Variables for a 3x3 matrix where rows should be lexicographically ordered
rows = [[model.int_var(0, 9, name=f"x_{i}_{j}") for j in range(3)] for i in range(3)]

# Break row symmetry: row[0] ≤ row[1] ≤ row[2] lexicographically
model.lex_le(rows[0], rows[1])
model.lex_le(rows[1], rows[2])

See also

lex_lt(lhs: Iterable[IntExpr | int], rhs: Iterable[IntExpr | int]) Constraint

Lexicographic strictly less than constraint: lhs < rhs.

Parameters:
  • lhs (Iterable[IntExpr | int]) – The left-hand side array of integer expressions.

  • rhs (Iterable[IntExpr | int]) – The right-hand side array of integer expressions.

Return type:

Constraint

Returns:

The lexicographic constraint.

Details

Constrains lhs to be lexicographically strictly less than rhs.

Both arrays must have the same length, and the length must be at least 1.

Lexicographic ordering compares arrays element by element from the first position. The comparison lhs < rhs holds if and only if: there exists a position k where lhs[k] < rhs[k] and all preceding elements are equal (lhs[i] == rhs[i] for all i < k)

Lexicographic constraints are useful for symmetry breaking. For example, when you have multiple equivalent solutions that differ only in the ordering of symmetric variables, adding a lexicographic constraint can eliminate redundant solutions.

Example

model = cp.Model()

# Variables for a 3x3 matrix where rows should be lexicographically ordered
rows = [[model.int_var(0, 9, name=f"x_{i}_{j}") for j in range(3)] for i in range(3)]

# Break row symmetry: row[0] < row[1] < row[2] lexicographically
model.lex_lt(rows[0], rows[1])
model.lex_lt(rows[1], rows[2])

See also

lex_ge(lhs: Iterable[IntExpr | int], rhs: Iterable[IntExpr | int]) Constraint

Lexicographic greater than or equal constraint: lhsrhs.

Parameters:
  • lhs (Iterable[IntExpr | int]) – The left-hand side array of integer expressions.

  • rhs (Iterable[IntExpr | int]) – The right-hand side array of integer expressions.

Return type:

Constraint

Returns:

The lexicographic constraint.

Details

Constrains lhs to be lexicographically greater than or equal rhs.

Both arrays must have the same length, and the length must be at least 1.

Lexicographic ordering compares arrays element by element from the first position. The comparison lhsrhs holds if and only if:

  • all elements are equal (lhs[i] == rhs[i] for all i), or

  • there exists a position k where lhs[k] > rhs[k] and all preceding elements are equal (lhs[i] == rhs[i] for all i < k)

Lexicographic constraints are useful for symmetry breaking. For example, when you have multiple equivalent solutions that differ only in the ordering of symmetric variables, adding a lexicographic constraint can eliminate redundant solutions.

Example

model = cp.Model()

# Variables for a 3x3 matrix where rows should be lexicographically ordered
rows = [[model.int_var(0, 9, name=f"x_{i}_{j}") for j in range(3)] for i in range(3)]

# Break row symmetry: row[0] ≥ row[1] ≥ row[2] lexicographically
model.lex_ge(rows[0], rows[1])
model.lex_ge(rows[1], rows[2])

See also

lex_gt(lhs: Iterable[IntExpr | int], rhs: Iterable[IntExpr | int]) Constraint

Lexicographic strictly greater than constraint: lhs > rhs.

Parameters:
  • lhs (Iterable[IntExpr | int]) – The left-hand side array of integer expressions.

  • rhs (Iterable[IntExpr | int]) – The right-hand side array of integer expressions.

Return type:

Constraint

Returns:

The lexicographic constraint.

Details

Constrains lhs to be lexicographically strictly greater than rhs.

Both arrays must have the same length, and the length must be at least 1.

Lexicographic ordering compares arrays element by element from the first position. The comparison lhs > rhs holds if and only if: there exists a position k where lhs[k] > rhs[k] and all preceding elements are equal (lhs[i] == rhs[i] for all i < k)

Lexicographic constraints are useful for symmetry breaking. For example, when you have multiple equivalent solutions that differ only in the ordering of symmetric variables, adding a lexicographic constraint can eliminate redundant solutions.

Example

model = cp.Model()

# Variables for a 3x3 matrix where rows should be lexicographically ordered
rows = [[model.int_var(0, 9, name=f"x_{i}_{j}") for j in range(3)] for i in range(3)]

# Break row symmetry: row[0] > row[1] > row[2] lexicographically
model.lex_gt(rows[0], rows[1])
model.lex_gt(rows[1], rows[2])

See also

start(interval: IntervalVar) IntExpr

Creates an integer expression for the start time of an interval variable.

Parameters:

interval (IntervalVar) – The interval variable.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

If the interval is absent, the resulting expression is also absent.

Example

In the following example, we constrain interval variable y to start after the end of x with a delay of at least 10. In addition, we constrain the length of x to be less or equal to the length of y.

import optalcp as cp

model = cp.Model()
x = model.interval_var(name="x", ...)
y = model.interval_var(name="y", ...)
model.enforce(model.end(x) + 10 <= model.start(y))
model.enforce(model.length(x) <= model.length(y))

When x or y is absent then value of both constraints above is absent and therefore they are satisfied.

See also

end(interval: IntervalVar) IntExpr

Creates an integer expression for the end time of an interval variable.

Parameters:

interval (IntervalVar) – The interval variable.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

If the interval is absent, the resulting expression is also absent.

Example

In the following example, we constrain interval variable y to start after the end of x with a delay of at least 10. In addition, we constrain the length of x to be less or equal to the length of y.

import optalcp as cp

model = cp.Model()
x = model.interval_var(name="x", ...)
y = model.interval_var(name="y", ...)
model.enforce(model.end(x) + 10 <= model.start(y))
model.enforce(model.length(x) <= model.length(y))

When x or y is absent then value of both constraints above is absent and therefore they are satisfied.

See also

length(interval: IntervalVar) IntExpr

Creates an integer expression for the duration (end - start) of an interval variable.

Parameters:

interval (IntervalVar) – The interval variable.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

If the interval is absent, the resulting expression is also absent.

Example

In the following example, we constrain interval variable y to start after the end of x with a delay of at least 10. In addition, we constrain the length of x to be less or equal to the length of y.

import optalcp as cp

model = cp.Model()
x = model.interval_var(name="x", ...)
y = model.interval_var(name="y", ...)
model.enforce(model.end(x) + 10 <= model.start(y))
model.enforce(model.length(x) <= model.length(y))

When x or y is absent then value of both constraints above is absent and therefore they are satisfied.

See also

end_before_end(predecessor: IntervalVar, successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • predecessor (IntervalVar) – The predecessor interval variable.

  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Same as:

model.enforce(predecessor.end() + delay <= successor.end())

In other words, end of predecessor plus delay must be less than or equal to end of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

end_before_start(predecessor: IntervalVar, successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • predecessor (IntervalVar) – The predecessor interval variable.

  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Same as:

model.enforce(predecessor.end() + delay <= successor.start())

In other words, end of predecessor plus delay must be less than or equal to start of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

start_before_end(predecessor: IntervalVar, successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • predecessor (IntervalVar) – The predecessor interval variable.

  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Same as:

model.enforce(predecessor.start() + delay <= successor.end())

In other words, start of predecessor plus delay must be less than or equal to end of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

start_before_start(predecessor: IntervalVar, successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • predecessor (IntervalVar) – The predecessor interval variable.

  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Same as:

model.enforce(predecessor.start() + delay <= successor.start())

In other words, start of predecessor plus delay must be less than or equal to start of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

end_at_end(predecessor: IntervalVar, successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • predecessor (IntervalVar) – The predecessor interval variable.

  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Same as:

model.enforce(predecessor.end() + delay == successor.end())

In other words, end of predecessor plus delay must be equal to end of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

end_at_start(predecessor: IntervalVar, successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • predecessor (IntervalVar) – The predecessor interval variable.

  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Same as:

model.enforce(predecessor.end() + delay == successor.start())

In other words, end of predecessor plus delay must be equal to start of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

start_at_end(predecessor: IntervalVar, successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • predecessor (IntervalVar) – The predecessor interval variable.

  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Same as:

model.enforce(predecessor.start() + delay == successor.end())

In other words, start of predecessor plus delay must be equal to end of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

start_at_start(predecessor: IntervalVar, successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • predecessor (IntervalVar) – The predecessor interval variable.

  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Same as:

model.enforce(predecessor.start() + delay == successor.start())

In other words, start of predecessor plus delay must be equal to start of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

alternative(main: IntervalVar, options: Iterable[IntervalVar]) Constraint

Alternative constraint models a choice between different ways to execute an interval.

Parameters:
  • main (IntervalVar) – The main interval variable.

  • options (Iterable[IntervalVar]) – Array of optional interval variables to choose from.

Return type:

Constraint

Returns:

The alternative constraint.

Details

Alternative constraint is a way to model various kinds of choices. For example, we can model a task that could be done by worker A, B, or C. To model such alternative, we use interval variable main that represents the task regardless the chosen worker and three interval variables options = [A, B, C] that represent the task when done by worker A, B, or C. Interval variables A, B, and C should be optional. This way, if e.g. option B is chosen, then B will be present and equal to main (they will start at the same time and end at the same time), the remaining options, A and C, will be absent.

We may also decide not to execute the main task at all (if it is optional). Then main will be absent and all options A, B and C will be absent too.

Formal definition

The constraint alternative(main, options) is satisfied in the following two cases: 1. Interval main is absent and all options[i] are absent too. 2. Interval main is present and exactly one of options[i] is present (the remaining options are absent). Let k be the index of the present option. Then main.start() == options[k].start() and main.end() == options[k].end().

Example

Let’s consider task T, which can be done by workers A, B, or C. The length of the task and a cost associated with it depends on the chosen worker:

  • If done by worker A, then its length is 10, and the cost is 5.

  • If done by worker B, then its length is 20, and the cost is 2.

  • If done by worker C, then its length is 3, and the cost is 10.

Each worker can execute only one task at a time. However, the remaining tasks are omitted in the model below. The objective could be, e.g., to minimize the total cost (also omitted in the model).

import optalcp as cp

model = cp.Model()

T = model.interval_var(name="T")
T_A = model.interval_var(name="T_A", optional=True, length=10)
T_B = model.interval_var(name="T_B", optional=True, length=20)
T_C = model.interval_var(name="T_C", optional=True, length=3)

# T_A, T_B and T_C are different ways to execute task T:
model.alternative(T, [T_A, T_B, T_C])
# The cost depends on the chosen option:
cost_of_t = model.sum([
    T_A.presence() * 5,
    T_B.presence() * 2,
    T_C.presence() * 10
])

# Each worker A can perform only one task at a time:
model.no_overlap([T_A, ...])  # Worker A
model.no_overlap([T_B, ...])  # Worker B
model.no_overlap([T_C, ...])  # Worker C

# Minimize the total cost:
model.minimize(model.sum([cost_of_t, ...]))
span(main: IntervalVar, covered: Iterable[IntervalVar]) Constraint

Constrains an interval variable to span (cover) a set of other interval variables.

Parameters:
  • main (IntervalVar) – The spanning interval variable.

  • covered (Iterable[IntervalVar]) – The set of interval variables to cover.

Return type:

Constraint

Returns:

The span constraint.

Details

Span constraint can be used to model, for example, a composite task that consists of several subtasks.

The constraint makes sure that interval variable main starts with the first interval in covered and ends with the last interval in covered. Absent interval variables in covered are ignored.

Formal definition

Span constraint is satisfied in one of the following two cases:

  • Interval variable main is absent and all interval variables in covered are absent too.

  • Interval variable main is present, at least one interval in covered is present and:

    • main.start() is equal to the minimum starting time of all present intervals in covered.

    • main.end() is equal to the maximum ending time of all present intervals in covered.

Example

Let’s consider composite task T, which consists of 3 subtasks: T1, T2, and T3. Subtasks are independent, could be processed in any order, and may overlap. However, task T is blocking a particular location, and no other task can be processed there. The location is blocked as soon as the first task from T1, T2, T3 starts, and it remains blocked until the last one of them finishes.

import optalcp as cp

model = cp.Model()

# Subtasks have known lengths:
T1 = model.interval_var(name="T1", length=10)
T2 = model.interval_var(name="T2", length=5)
T3 = model.interval_var(name="T3", length=15)
# The main task has unknown length though:
T = model.interval_var(name="T")

# T spans/covers T1, T2 and T3:
model.span(T, [T1, T2, T3])

# Tasks requiring the same location cannot overlap.
# Other tasks are not included in the example, therefore '...' below:
model.no_overlap([T, ...])

See also

position(interval: IntervalVar, sequence: SequenceVar) IntExpr

Creates an expression equal to the position of the interval on the sequence.

Parameters:
Return type:

IntExpr

Returns:

The resulting integer expression

Details

In the solution, the interval which is scheduled first has position 0, the second interval has position 1, etc. The position of an absent interval is absent.

The position expression cannot be used with interval variables of possibly zero length (because the position of two simultaneous zero-length intervals would be undefined). Also, position cannot be used in case of Model.no_overlap() constraint with transition times.

See also

pulse(interval: IntervalVar, height: IntExpr | int) CumulExpr

Creates cumulative function (expression) _pulse_ for the given interval variable and height.

Parameters:
Return type:

CumulExpr

Returns:

The resulting cumulative expression

Details

Pulse can be used to model a resource requirement during an interval variable. The given amount height of the resource is used throughout the interval (from start to end).

Limitation: The height must be non-negative. Pulses with negative height are not supported. If you need negative contributions, use step functions instead (see Model.step_at_start() and Model.step_at_end()).

Formal definition

Pulse creates a cumulative function which has the value:

  • 0 before interval.start(),

  • height between interval.start() and interval.end(),

  • 0 after interval.end()

If interval is absent, the pulse is 0 everywhere.

The height can be a constant value or an expression. In particular, the height can be given by an IntVar. In such a case, the height is unknown at the time of the model creation but is determined during the search.

Note that the interval and the height may have different presence statuses (when the height is given by a variable or an expression). In this case, the pulse is present only if both the interval and the height are present. Therefore, it is helpful to constrain the height to have the same presence status as the interval.

Cumulative functions can be combined using operators (+, -, unary -) and Model.sum(). A cumulative function’s minimum and maximum height can be constrained using comparison operators (<=, >=).

Example

Let us consider a set of tasks and a group of 3 workers. Each task requires a certain number of workers (demand). Our goal is to schedule the tasks so that the length of the schedule (makespan) is minimal.

import optalcp as cp

# The input data:
nb_workers = 3
tasks = [
  { "length": 10, "demand": 3},
  { "length": 20, "demand": 2},
  { "length": 15, "demand": 1},
  { "length": 30, "demand": 2},
  { "length": 20, "demand": 1},
  { "length": 25, "demand": 2},
  { "length": 10, "demand": 1},
]

model = cp.Model()
# A set of pulses, one for each task:
pulses = []
# End times of the tasks:
ends = []

for i in range(len(tasks)):
  # Create a task:
  task = model.interval_var(name=f"T{i+1}", length=tasks[i]["length"])
  # Create a pulse for the task:
  pulses.append(model.pulse(task, tasks[i]["demand"]))
  # Store the end of the task:
  ends.append(task.end())

# The number of workers used at any time cannot exceed nb_workers:
model.enforce(model.sum(pulses) <= nb_workers)
# Minimize the maximum of the ends (makespan):
model.minimize(model.max(ends))

result = model.solve({'searchType': 'FDS'})

Example

In the following example, we create three interval variables x, y, and z that represent some tasks. Variables x and y are present, but variable z is optional. Each task requires a certain number of workers. The length of the task depends on the assigned number of workers. The number of assigned workers is modeled using integer variables wx, wy, and wz.

There are 7 workers. Therefore, at any time, the sum of the workers assigned to the running tasks must be less or equal to 7.

If the task z is absent, then the variable wz has no meaning, and therefore, it should also be absent.

import optalcp as cp

model = cp.Model()
x = model.interval_var(name="x")
y = model.interval_var(name="y")
z = model.interval_var(name="z", optional=True)

wx = model.int_var(min=1, max=5, name="wx")
wy = model.int_var(min=1, max=5, name="wy")
wz = model.int_var(min=1, max=5, name="wz", optional=True)

# wz is present if and only if z is present:
model.enforce(z.presence() == wz.presence())

px = model.pulse(x, wx)
py = model.pulse(y, wy)
pz = model.pulse(z, wz)

# There are at most 7 workers at any time:
model.enforce(model.sum([px, py, pz]) <= 7)

# Length of the task depends on the number of workers using the following formula:
#    length * wx = 12
model.enforce(x.length() * wx == 12)
model.enforce(y.length() * wy == 12)
model.enforce(z.length() * wz == 12)

See also

step_at_start(interval: IntervalVar, height: IntExpr | int) CumulExpr

Creates cumulative function (expression) that changes value at start of the interval variable by the given height.

Parameters:
Return type:

CumulExpr

Returns:

The resulting cumulative expression

Details

Cumulative step functions could be used to model a resource that is consumed or produced and, therefore, changes in amount over time. Examples of such a resource are a battery, an account balance, a product’s stock, etc.

A step_at_start can change the amount of such resource at the start of a given variable. The amount is changed by the given height, which can be positive or negative.

The height can be a constant value or an expression. In particular, the height can be given by an IntVar. In such a case, the height is unknown at the time of the model creation but is determined during the search.

Note that the interval and the height may have different presence statuses (when the height is given by a variable or an expression). In this case, the step is present only if both the interval and the height are present. Therefore, it is helpful to constrain the height to have the same presence status as the interval.

Cumulative steps can be combined using operators (+, -, unary -) and Model.sum(). A cumulative function’s minimum and maximum height can be constrained using comparison operators (<=, >=).

Formal definition

stepAtStart creates a cumulative function which has the value:

  • 0 before interval.start(),

  • height after interval.start().

If the interval or the height is absent, the created cumulative function is 0 everywhere.

Example

Let us consider a set of tasks. Each task either costs a certain amount of money or makes some money. Money is consumed at the start of a task and produced at the end. We have an initial budget, and we want to schedule the tasks so that we do not run out of money (i.e., the amount is always non-negative).

Tasks cannot overlap. Our goal is to find the shortest schedule possible.

import optalcp as cp

# The input data:
budget = 100
tasks = [
    {"length": 10, "money": -150},
    {"length": 20, "money":   40},
    {"length": 15, "money":   20},
    {"length": 30, "money":  -10},
    {"length": 20, "money":   30},
    {"length": 25, "money":  -20},
    {"length": 10, "money":   10},
    {"length": 20, "money":   50},
]

model = cp.Model()
task_vars = []
# A set of steps, one for each task:
steps = []

for i in range(len(tasks)):
    interval = model.interval_var(name=f"T{i+1}", length=tasks[i]["length"])
    task_vars.append(interval)
    if tasks[i]["money"] < 0:
        # Task costs some money:
        steps.append(model.step_at_start(interval, tasks[i]["money"]))
    else:
        # Tasks makes some money:
        steps.append(model.step_at_end(interval, tasks[i]["money"]))

# The initial budget increases the cumul at time 0:
steps.append(model.step_at(0, budget))
# The money must be non-negative at any time:
model.enforce(model.sum(steps) >= 0)
# Only one task at a time:
model.no_overlap(task_vars)

# Minimize the maximum of the ends (makespan):
model.minimize(model.max([t.end() for t in task_vars]))

result = model.solve({'searchType': 'FDS'})

See also

step_at_end(interval: IntervalVar, height: IntExpr | int) CumulExpr

Creates cumulative function (expression) that changes value at end of the interval variable by the given height.

Parameters:
Return type:

CumulExpr

Returns:

The resulting cumulative expression

Details

Cumulative step functions could be used to model a resource that is consumed or produced and, therefore, changes in amount over time. Examples of such a resource are a battery, an account balance, a product’s stock, etc.

A step_at_end can change the amount of such resource at the end of a given variable. The amount is changed by the given height, which can be positive or negative.

The height can be a constant value or an expression. In particular, the height can be given by an IntVar. In such a case, the height is unknown at the time of the model creation but is determined during the search.

Note that the interval and the height may have different presence statuses (when the height is given by a variable or an expression). In this case, the step is present only if both the interval and the height are present. Therefore, it is helpful to constrain the height to have the same presence status as the interval.

Cumulative steps can be combined using operators (+, -, unary -) and Model.sum(). A cumulative function’s minimum and maximum height can be constrained using comparison operators (<=, >=).

Formal definition

stepAtEnd creates a cumulative function which has the value:

  • 0 before interval.end(),

  • height after interval.end().

If the interval or the height is absent, the created cumulative function is 0 everywhere.

Example

Let us consider a set of tasks. Each task either costs a certain amount of money or makes some money. Money is consumed at the start of a task and produced at the end. We have an initial budget, and we want to schedule the tasks so that we do not run out of money (i.e., the amount is always non-negative).

Tasks cannot overlap. Our goal is to find the shortest schedule possible.

import optalcp as cp

# The input data:
budget = 100
tasks = [
    {"length": 10, "money": -150},
    {"length": 20, "money":   40},
    {"length": 15, "money":   20},
    {"length": 30, "money":  -10},
    {"length": 20, "money":   30},
    {"length": 25, "money":  -20},
    {"length": 10, "money":   10},
    {"length": 20, "money":   50},
]

model = cp.Model()
task_vars = []
# A set of steps, one for each task:
steps = []

for i in range(len(tasks)):
    interval = model.interval_var(name=f"T{i+1}", length=tasks[i]["length"])
    task_vars.append(interval)
    if tasks[i]["money"] < 0:
        # Task costs some money:
        steps.append(model.step_at_start(interval, tasks[i]["money"]))
    else:
        # Tasks makes some money:
        steps.append(model.step_at_end(interval, tasks[i]["money"]))

# The initial budget increases the cumul at time 0:
steps.append(model.step_at(0, budget))
# The money must be non-negative at any time:
model.enforce(model.sum(steps) >= 0)
# Only one task at a time:
model.no_overlap(task_vars)

# Minimize the maximum of the ends (makespan):
model.minimize(model.max([t.end() for t in task_vars]))

result = model.solve({'searchType': 'FDS'})

See also

step_at(x: int, height: IntExpr | int) CumulExpr

Creates a cumulative function that changes value at a given point.

Parameters:
  • x (int) – The point at which the cumulative function changes value.

  • height (IntExpr | int) – The height value (can be positive, negative, constant, or expression).

Return type:

CumulExpr

Returns:

The resulting cumulative expression

Details

This function is similar to Model.step_at_start() and Model.step_at_end(), but the time of the change is given by the constant value x instead of by the start/end of an interval variable. The height can be a constant or an expression (e.g., created by Model.int_var()).

Formal definition

step_at creates a cumulative function which has the value:

  • 0 before x,

  • height after x.

See also

integral(func: IntStepFunction, interval: IntervalVar) IntExpr

Computes sum of values of the step function func over the interval interval.

Parameters:
Return type:

IntExpr

Returns:

The resulting integer expression

Details

The sum is computed over all points in range interval.start() .. interval.end()-1. The sum includes the function value at the start time but not the value at the end time. If the interval variable has zero length, then the result is 0. If the interval variable is absent, then the result is absent.

Requirement: The step function func must be non-negative.

See also

eval(func: IntStepFunction, arg: IntExpr | int) IntExpr

Evaluates a step function at a given point.

Parameters:
Return type:

IntExpr

Returns:

The resulting integer expression

Details

The result is the value of the step function func at the point arg. If the value of arg is absent, then the result is also absent.

By constraining the returned value, it is possible to limit arg to be only within certain segments of the segmented function. In particular, functions Model.forbid_start() and Model.forbid_end() work that way.

See also

forbid_extent(interval: IntervalVar, func: IntStepFunction) Constraint

Forbid the interval variable to overlap with segments of the function where the value is zero.

Parameters:
Return type:

Constraint

Returns:

The constraint forbidding the extent (entire interval).

Details

This function prevents the specified interval variable from overlapping with segments of the step function where the value is zero. That is, if \([s, e)\) is a segment of the step function where the value is zero, then the interval variable either ends before \(s\) (\(\mathtt{interval.end()} \le s\)) or starts after \(e\) (\(e \le \mathtt{interval.start()}\)).

Example

A production task that cannot overlap with scheduled maintenance windows:

import optalcp as cp

model = cp.Model()

# A 3-hour production run
production = model.interval_var(length=3, name="production")

# Machine availability: 1 = available, 0 = under maintenance
availability = model.step_function([
    (0, 1),   # Initially available
    (8, 0),   # 8h: maintenance starts
    (10, 1),  # 10h: maintenance ends
])

# Production cannot overlap periods where availability is 0
model.forbid_extent(production, availability)
model.minimize(production.end())

result = model.solve()
# Production runs [0, 3) - finishes before maintenance window

See also

forbid_start(interval: IntervalVar, func: IntStepFunction) Constraint

Constrains the start of the interval variable to be outside of the zero-height segments of the step function.

Parameters:
Return type:

Constraint

Returns:

The constraint forbidding the start point.

Details

This function is equivalent to:

model.enforce(model.eval(func, interval.start()) != 0)

I.e., the function value at the start of the interval variable cannot be zero.

Example

A factory task that can only start during work hours (excluding breaks):

import optalcp as cp

model = cp.Model()

# A 2-hour task on a machine
task = model.interval_var(length=2, name="task")

# Allowed start times: 1 = allowed, 0 = forbidden
# Morning shift 6-14h, but break at 10-11h when no new task can start
allowed_starts = model.step_function([
    (0, 0),   # Before 6h: forbidden
    (6, 1),   # 6h: shift starts, allowed
    (10, 0),  # 10h: break, forbidden
    (11, 1),  # 11h: break ends, allowed
    (14, 0),  # 14h: shift ends, forbidden
])

# Task cannot start when allowed_starts is 0
model.forbid_start(task, allowed_starts)
model.minimize(task.start())

result = model.solve()
# Task starts at 6 (earliest allowed start time)

See also

forbid_end(interval: IntervalVar, func: IntStepFunction) Constraint

Constrains the end of the interval variable to be outside of the zero-height segments of the step function.

Parameters:
Return type:

Constraint

Returns:

The constraint forbidding the end point.

Details

This function is equivalent to:

model.enforce(model.eval(func, interval.end()) != 0)

I.e., the function value at the end of the interval variable cannot be zero.

Example

A delivery task that must complete during business hours (not during lunch break):

import optalcp as cp

model = cp.Model()

# A 1-hour delivery task
delivery = model.interval_var(length=1, name="delivery")

# Allowed end times: 1 = allowed, 0 = forbidden
# Business hours 9-17h, but deliveries cannot end during lunch 12-13h
allowed_ends = model.step_function([
    (0, 0),   # Before 9h: forbidden
    (9, 1),   # 9h: business opens, allowed
    (12, 0),  # 12h: lunch break, forbidden
    (13, 1),  # 13h: lunch ends, allowed
    (17, 0),  # 17h: business closes, forbidden
])

# Delivery cannot end when allowed_ends is 0
model.forbid_end(delivery, allowed_ends)
model.minimize(delivery.end())

result = model.solve()
# Delivery ends at 9 (starts at 8, ends at earliest allowed time)

See also

Variables

class optalcp.IntVar

Bases: IntExpr

Integer variable represents an unknown (integer) value that solver has to find.

The value of the integer variable can be constrained using arithmetic operators (+, -, *, //) and comparison operators (<, <=, ==, !=, >, >=).

OptalCP solver focuses on scheduling problems and concentrates on IntervalVar variables. Therefore, interval variables should be the primary choice for modeling in OptalCP. However, integer variables can be used for other purposes, such as counting or indexing. In particular, integer variables can be helpful for cumulative expressions with variable heights; see Model.pulse(), Model.step_at_start(), Model.step_at_end(), and Model.step_at().

The integer variable can be optional. In this case, the solver can make the variable absent, which is usually interpreted as the fact that the solver does not use the variable at all. Functions Model.presence() and IntExpr.presence() can constrain the presence of the variable.

Integer variables can be created using the function Model.int_var().

Example

In the following example we create three integer variables x, y and z. Variables x and y are present, but variable z is optional. Each variable has a different range of possible values.

import optalcp as cp

model = cp.Model()
x = model.int_var(name="x", min=1, max=3)
y = model.int_var(name="y", min=0, max=100)
z = model.int_var(name="z", min=10, max=20, optional=True)
property min: int | None

The minimum value of the integer variable’s domain.

Gets or sets the minimum value of the integer variable’s domain.

The initial value is set during construction by Model.int_var(). If the variable is absent, the getter returns None.

Note: This property reflects the variable’s domain in the model (before the solve), not in the solution.

The value must be in the range IntVarMin to IntVarMax.

Example

import optalcp as cp

model = cp.Model()
x = model.int_var(min=5, max=10, name="x")

print(x.min)  # 5

x.min = 7
print(x.min)  # 7
property max: int | None

The maximum value of the integer variable’s domain.

Gets or sets the maximum value of the integer variable’s domain.

The initial value is set during construction by Model.int_var(). If the variable is absent, the getter returns None.

Note: This property reflects the variable’s domain in the model (before the solve), not in the solution.

The value must be in the range IntVarMin to IntVarMax.

Example

import optalcp as cp

model = cp.Model()
x = model.int_var(min=5, max=10, name="x")

print(x.max)  # 10

x.max = 8
print(x.max)  # 8
property optional: bool | None

The presence status of the integer variable.

Gets or sets the presence status of the integer variable using a tri-state value:

  • True / True: The variable is optional - the solver decides whether it is present or absent in the solution.

  • False / False: The variable is present - it must have a value in the solution.

  • None / None: The variable is absent - it will be omitted from the solution.

Note: This property reflects the presence status in the model (before the solve), not in the solution.

Example

import optalcp as cp

model = cp.Model()
x = model.int_var(min=0, max=10, name="x")
y = model.int_var(min=0, max=10, optional=True, name="y")

print(x.optional)  # False (present by default)
print(y.optional)  # True (optional)

# Make x optional
x.optional = True
print(x.optional)  # True

# Make y absent
y.optional = None
print(y.optional)  # None

See also

class optalcp.BoolVar

Bases: BoolExpr

Boolean variable represents an unknown truth value (True or False) that the solver must find.

Boolean variables are useful for modeling decisions, choices, or logical conditions in your problem. For example, you can use boolean variables to represent whether a machine is used, whether a task is assigned to a particular worker, or whether a constraint should be enforced.

Boolean variables can be created using the function Model.bool_var(). By default, boolean variables are present (not optional). To create an optional boolean variable, specify optional=True in the arguments of the function.

Logical operators

Boolean variables support the following logical operators:

  • ~x for logical NOT

  • x | y for logical OR

  • x & y for logical AND

These operators can be used to create complex boolean expressions and constraints.

Boolean variables as integer expressions

Class BoolVar derives from BoolExpr, which derives from IntExpr. Therefore, boolean variables can be used as integer expressions: True is equal to 1, False is equal to 0, and absent remains absent.

This is useful for counting how many conditions are satisfied or for weighted sums.

Optional boolean variables

A boolean variable can be optional. In this case, the solver can decide to make the variable absent, which means the variable doesn’t participate in the solution. When a boolean variable is absent, its value is neither True nor False — it is absent.

Most expressions that depend on an absent variable are also absent. For example, if x is an absent boolean variable, then ~x, x | y, and x & y are all absent, regardless of the value of y. However, some functions handle absent values specially, such as IntExpr.presence() or Model.sum().

When a boolean expression is added as a constraint using Model.enforce(), the constraint requires that the expression is not False in the solution. The expression can be True or absent. This means that constraints involving optional variables are automatically satisfied when the underlying variables are absent.

Functions Model.presence() and IntExpr.presence() can constrain the presence of the variable.

Example

In the following example, we create two boolean variables representing whether to use each of two machines. We require that at least one machine is used, but not both:

import optalcp as cp

model = cp.Model()
use_machine_a = model.bool_var(name="use_machine_a")
use_machine_b = model.bool_var(name="use_machine_b")

# Constraint: must use at least one machine
model.enforce(use_machine_a | use_machine_b)

# Constraint: cannot use both machines (exclusive choice)
model.enforce(~(use_machine_a & use_machine_b))

result = model.solve()

Example

Boolean variables can be used in arithmetic expressions by treating True as 1 and False as 0:

import optalcp as cp

model = cp.Model()
options = [model.bool_var(name=f"option_{i}") for i in range(5)]

# Constraint: select exactly 2 options
model.enforce(model.sum(options) == 2)

result = model.solve()

See also

  • Model.bool_var() to create boolean variables.

  • BoolExpr for boolean expressions and their operations.

  • IntVar for integer decision variables.

  • IntervalVar for the primary variable type for scheduling problems.

property min: bool | None

The minimum value of the boolean variable’s domain.

Gets or sets the minimum value of the boolean variable’s domain.

For a free (unconstrained) boolean variable, returns False. If set to True, the variable is fixed to True. If the variable is absent, the getter returns None.

Note: This property reflects the variable’s domain in the model (before the solve), not in the solution.

Example
import optalcp as cp

model = cp.Model()
x = model.bool_var(name="x")

print(x.min)  # False (default minimum)

x.min = True
print(x.min)  # True (variable is now fixed to True)
property max: bool | None

The maximum value of the boolean variable’s domain.

Gets or sets the maximum value of the boolean variable’s domain.

For a free (unconstrained) boolean variable, returns True. If set to False, the variable is fixed to False. If the variable is absent, the getter returns None.

Note: This property reflects the variable’s domain in the model (before the solve), not in the solution.

Example
import optalcp as cp

model = cp.Model()
x = model.bool_var(name="x")

print(x.max)  # True (default maximum)

x.max = False
print(x.max)  # False (variable is now fixed to False)
property optional: bool | None

The presence status of the boolean variable.

Gets or sets the presence status of the boolean variable using a tri-state value:

  • True / True: The variable is optional - the solver decides whether it is present or absent in the solution.

  • False / False: The variable is present - it must have a value in the solution.

  • None / None: The variable is absent - it will be omitted from the solution.

Note: This property reflects the presence status in the model (before the solve), not in the solution.

Example
import optalcp as cp

model = cp.Model()
x = model.bool_var(name="x")
y = model.bool_var(optional=True, name="y")

print(x.optional)  # False (present by default)
print(y.optional)  # True (optional)

# Make x optional
x.optional = True
print(x.optional)  # True

# Make y absent
y.optional = None
print(y.optional)  # None
class optalcp.IntervalVar

Bases: ModelElement

Interval variable is a task, action, operation, or any other interval with a start and an end. The start and the end of the interval are unknowns that the solver has to find. They could be accessed as integer expressions using IntervalVar.start() and IntervalVar.end(). or using Model.start() and Model.end(). In addition to the start and the end of the interval, the interval variable has a length (equal to end - start) that can be accessed using IntervalVar.length() or Model.length().

The interval variable can be optional. In this case, the solver can decide to make the interval absent, which is usually interpreted as the fact that the interval doesn’t exist, the task/action was not executed, or the operation was not performed. When the interval variable is absent, its start, end, and length are also absent. A boolean expression that represents the presence of the interval variable can be accessed using IntervalVar.presence() and Model.presence().

Interval variables can be created using the function Model.interval_var(). By default, interval variables are present (not optional). To create an optional interval, specify optional: true in the arguments of the function.

Example

In the following example we create three present interval variables x, y and z and we make sure that they don’t overlap. Then, we minimize the maximum of the end times of the three intervals (the makespan):

import optalcp as cp

model = cp.Model()
x = model.interval_var(length=10, name="x")
y = model.interval_var(length=10, name="y")
z = model.interval_var(length=10, name="z")
model.no_overlap([x, y, z])
model.minimize(model.max([x.end(), y.end(), z.end()]))
result = model.solve()

Example

In the following example, there is a task X that could be performed by two different workers A and B. The interval variable X represents the task. It is not optional because the task X is mandatory. Interval variable XA represents the task X when performed by worker A and similarly XB represents the task X when performed by worker B. Both XA and XB are optional because it is not known beforehand which worker will perform the task. The constraint IntervalVar.alternative() links X, XA and XB together and ensures that only one of XA and XB is present and that X and the present interval are equal.

import optalcp as cp

model = cp.Model()
X = model.interval_var(length=10, name="X")
XA = model.interval_var(name="XA", optional=True)
XB = model.interval_var(name="XB", optional=True)
model.alternative(X, [XA, XB])
result = model.solve()

Variables XA and XB can be used elsewhere in the model, e.g. to make sure that each worker is assigned to at most one task at a time:

# Tasks of worker A don't overlap:
model.no_overlap([... , XA, ...])
# Tasks of worker B don't overlap:
model.no_overlap([... , XB, ...])
presence() BoolExpr

Creates a Boolean expression which is true if the interval variable is present.

Return type:

BoolExpr

Returns:

A Boolean expression that is true if the interval variable is present in the solution.

Details

The resulting expression is never absent: it is True if the interval variable is present and False if the interval variable is absent.

This function is the same as Model.presence(), see its documentation for more details.

Example

In the following example, interval variables x and y must have the same presence status. I.e. they must either be both present or both absent.

model = cp.Model()
x = model.interval_var(name="x", optional=True, length=10)
y = model.interval_var(name="y", optional=True, length=10)
model.enforce(x.presence() == y.presence())

See also

start() IntExpr

Creates an integer expression for the start time of the interval variable.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

If the interval variable is absent, then the resulting expression is also absent.

Example

In the following example, we constrain interval variable y to start after the end of x with a delay of at least 10. In addition, we constrain the length of x to be less or equal to the length of y.

import optalcp as cp

model = cp.Model()
x = model.interval_var(name="x", ...)
y = model.interval_var(name="y", ...)
model.enforce(x.end() + 10 <= y.start())
model.enforce(x.length() <= y.length())

When x or y is absent then value of both constraints above is absent and therefore they are satisfied.

See also

end() IntExpr

Creates an integer expression for the end time of the interval variable.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

If the interval variable is absent, then the resulting expression is also absent.

Example

In the following example, we constrain interval variable y to start after the end of x with a delay of at least 10. In addition, we constrain the length of x to be less or equal to the length of y.

import optalcp as cp

model = cp.Model()
x = model.interval_var(name="x", ...)
y = model.interval_var(name="y", ...)
model.enforce(x.end() + 10 <= y.start())
model.enforce(x.length() <= y.length())

When x or y is absent then value of both constraints above is absent and therefore they are satisfied.

See also

length() IntExpr

Creates an integer expression for the duration (end - start) of the interval variable.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

If the interval variable is absent, then the resulting expression is also absent.

Example

In the following example, we constrain interval variable y to start after the end of x with a delay of at least 10. In addition, we constrain the length of x to be less or equal to the length of y.

import optalcp as cp

model = cp.Model()
x = model.interval_var(name="x", ...)
y = model.interval_var(name="y", ...)
model.enforce(x.end() + 10 <= y.start())
model.enforce(x.length() <= y.length())

When x or y is absent then value of both constraints above is absent and therefore they are satisfied.

See also

end_before_end(successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Assuming that the current interval is predecessor, the constraint is the same as:

predecessor.end() + delay <= successor.end()

In other words, end of predecessor plus delay must be less than or equal to end of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

See also

end_before_start(successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Assuming that the current interval is predecessor, the constraint is the same as:

predecessor.end() + delay <= successor.start()

In other words, end of predecessor plus delay must be less than or equal to start of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

start_before_end(successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Assuming that the current interval is predecessor, the constraint is the same as:

predecessor.start() + delay <= successor.end()

In other words, start of predecessor plus delay must be less than or equal to end of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

start_before_start(successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Assuming that the current interval is predecessor, the constraint is the same as:

predecessor.start() + delay <= successor.start()

In other words, start of predecessor plus delay must be less than or equal to start of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

end_at_end(successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Assuming that the current interval is predecessor, the constraint is the same as:

predecessor.end() + delay == successor.end()

In other words, end of predecessor plus delay must be equal to end of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

See also

end_at_start(successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Assuming that the current interval is predecessor, the constraint is the same as:

predecessor.end() + delay == successor.start()

In other words, end of predecessor plus delay must be equal to start of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

See also

start_at_end(successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Assuming that the current interval is predecessor, the constraint is the same as:

predecessor.start() + delay == successor.end()

In other words, start of predecessor plus delay must be equal to end of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

See also

start_at_start(successor: IntervalVar, delay: IntExpr | int = 0) Constraint

Creates a precedence constraint between two interval variables.

Parameters:
  • successor (IntervalVar) – The successor interval variable.

  • delay (IntExpr | int) – The minimum delay between intervals.

Return type:

Constraint

Returns:

The precedence constraint.

Details

Assuming that the current interval is predecessor, the constraint is the same as:

predecessor.start() + delay == successor.start()

In other words, start of predecessor plus delay must be equal to start of successor.

When one of the two interval variables is absent, then the constraint is satisfied.

See also

alternative(options: Iterable[IntervalVar]) Constraint

Creates alternative constraints for the interval variable and provided options.

Parameters:

options (Iterable[IntervalVar]) – The interval variables to choose from.

Return type:

Constraint

Returns:

The alternative constraint.

Details

The alternative constraint requires that exactly one of the options intervals is present when self is present. The selected option must have the same start, end, and length as self. If self is absent, all options must be absent.

This is useful for modeling choices, such as assigning a task to one of several machines, where each option represents the task executed on a different machine.

This constraint is equivalent to Model.alternative() with self as the main interval.

span(covered: Iterable[IntervalVar]) Constraint

Constrains the interval variable to span (cover) a set of other interval variables.

Parameters:

covered (Iterable[IntervalVar]) – The set of interval variables to cover.

Return type:

Constraint

Returns:

The span constraint.

Details

The span constraint ensures that self exactly covers all present intervals in covered. Specifically, self starts at the minimum start time and ends at the maximum end time of the present covered intervals. If all covered intervals are absent, self must also be absent.

This is useful for modeling composite tasks or projects where a parent interval represents the overall duration of multiple sub-tasks.

This constraint is equivalent to Model.span() with self as the spanning interval.

position(sequence: SequenceVar) IntExpr

Creates an expression equal to the position of the interval on the sequence.

Parameters:

sequence (SequenceVar) – The sequence variable.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

Returns an integer expression representing the 0-based position of this interval in the given sequence. The position reflects the order in which intervals appear after applying the SequenceVar.no_overlap() constraint.

If this interval is absent, the position expression is also absent.

This method is equivalent to Model.position() with self as the interval.

pulse(height: IntExpr | int) CumulExpr

Creates cumulative function (expression) pulse for the interval variable and specified height.

Parameters:

height (IntExpr | int) – The height value.

Return type:

CumulExpr

Returns:

The resulting cumulative expression

Details

Limitation: The height must be non-negative. Pulses with negative height are not supported.

This function is the same as Model.pulse().

Example

task = model.interval_var(name="task", length=10)
pulse = task.pulse(5)

See also

step_at_start(height: IntExpr | int) CumulExpr

Creates cumulative function (expression) that changes value at start of the interval variable by the given height.

Parameters:

height (IntExpr | int) – The height value.

Return type:

CumulExpr

Returns:

The resulting cumulative expression

Details

Creates cumulative function (expression) that changes value at start of the interval variable by the given height.

This function is the same as Model.step_at_start().

Example

task = model.interval_var(name="task", length=10)
step = task.step_at_start(5)

See also

step_at_end(height: IntExpr | int) CumulExpr

Creates cumulative function (expression) that changes value at end of the interval variable by the given height.

Parameters:

height (IntExpr | int) – The height value.

Return type:

CumulExpr

Returns:

The resulting cumulative expression

Details

Creates cumulative function (expression) that changes value at end of the interval variable by the given height.

This function is the same as Model.step_at_end().

Example

task = model.interval_var(name="task", length=10)
step = task.step_at_end(5)

See also

forbid_extent(func: IntStepFunction) Constraint

This function prevents the specified interval variable from overlapping with segments of the step function where the value is zero.

Parameters:

func (IntStepFunction) – The step function.

Return type:

Constraint

Returns:

The constraint forbidding the extent (entire interval).

Details

This function prevents the specified interval variable from overlapping with segments of the step function where the value is zero. I.e., if \([s, e)\) is a segment of the step function where the value is zero, then the interval variable either ends before \(s\) (\(\mathtt{interval.end()} \le s\)) or starts after \(e\) (\(e \le \mathtt{interval.start()}\)).

Example

A production task that cannot overlap with scheduled maintenance windows:

import optalcp as cp

model = cp.Model()

# A 3-hour production run
production = model.interval_var(length=3, name="production")

# Machine availability: 1 = available, 0 = under maintenance
availability = model.step_function([
    (0, 1),   # Initially available
    (8, 0),   # 8h: maintenance starts
    (10, 1),  # 10h: maintenance ends
])

# Production cannot overlap periods where availability is 0
production.forbid_extent(availability)
model.minimize(production.end())

result = model.solve()
# Production runs [0, 3) - finishes before maintenance window

See also

forbid_start(func: IntStepFunction) Constraint

Constrains the start of an interval variable to not coincide with zero segments of a step function.

Parameters:

func (IntStepFunction) – The step function whose zero segments define forbidden start times.

Return type:

Constraint

Returns:

The constraint forbidding the start point.

Details

This function is equivalent to:

model.enforce(func.eval(interval.start()) != 0)

I.e., the function value at the start of the interval variable cannot be zero.

Example

A factory task that can only start during work hours (excluding breaks):

import optalcp as cp

model = cp.Model()

# A 2-hour task on a machine
task = model.interval_var(length=2, name="task")

# Allowed start times: 1 = allowed, 0 = forbidden
# Morning shift 6-14h, but break at 10-11h when no new task can start
allowed_starts = model.step_function([
    (0, 0),   # Before 6h: forbidden
    (6, 1),   # 6h: shift starts, allowed
    (10, 0),  # 10h: break, forbidden
    (11, 1),  # 11h: break ends, allowed
    (14, 0),  # 14h: shift ends, forbidden
])

# Task cannot start when allowed_starts is 0
task.forbid_start(allowed_starts)
model.minimize(task.start())

result = model.solve()
# Task starts at 6 (earliest allowed start time)

See also

forbid_end(func: IntStepFunction) Constraint

Constrains the end of an interval variable to not coincide with zero segments of a step function.

Parameters:

func (IntStepFunction) – The step function whose zero segments define forbidden end times.

Return type:

Constraint

Returns:

The constraint forbidding the end point.

Details

This function is equivalent to:

model.enforce(func.eval(interval.end()) != 0)

I.e., the function value at the end of the interval variable cannot be zero.

Example

A delivery task that must complete during business hours (not during lunch break):

import optalcp as cp

model = cp.Model()

# A 1-hour delivery task
delivery = model.interval_var(length=1, name="delivery")

# Allowed end times: 1 = allowed, 0 = forbidden
# Business hours 9-17h, but deliveries cannot end during lunch 12-13h
allowed_ends = model.step_function([
    (0, 0),   # Before 9h: forbidden
    (9, 1),   # 9h: business opens, allowed
    (12, 0),  # 12h: lunch break, forbidden
    (13, 1),  # 13h: lunch ends, allowed
    (17, 0),  # 17h: business closes, forbidden
])

# Delivery cannot end when allowed_ends is 0
delivery.forbid_end(allowed_ends)
model.minimize(delivery.end())

result = model.solve()
# Delivery ends at 9 (starts at 8, ends at earliest allowed time)

See also

property optional: bool | None

The presence status of the interval variable.

Gets or sets the presence status of the interval variable using a tri-state value:

  • True / True: The interval is optional - the solver decides whether it is present or absent in the solution.

  • False / False: The interval is present - it must be scheduled in the solution.

  • None / None: The interval is absent - it will be omitted from the solution (and everything that depends on it).

Note: This property reflects the presence status in the model (before the solve), not in the solution.

Example

import optalcp as cp

model = cp.Model()
task1 = model.interval_var(length=10, name="task1")
task2 = model.interval_var(length=10, optional=True, name="task2")

print(task1.optional)  # False (present by default)
print(task2.optional)  # True (optional)

# Make task1 optional
task1.optional = True
print(task1.optional)  # True

# Make task2 absent
task2.optional = None
print(task2.optional)  # None
property start_min: int | None

The minimum start time of the interval variable.

Gets or sets the minimum start time of the interval variable.

The initial value is set during construction by Model.interval_var(). If the interval is absent, the getter returns None.

Note: This property reflects the interval’s domain in the model (before the solve), not in the solution.

The value must be in the range IntervalMin to IntervalMax.

Example

import optalcp as cp

model = cp.Model()
task = model.interval_var(length=10, name="task")

print(task.start_min)  # 0 (default)

task.start_min = 5
print(task.start_min)  # 5
property start_max: int | None

The maximum start time of the interval variable.

Gets or sets the maximum start time of the interval variable.

The initial value is set during construction by Model.interval_var(). If the interval is absent, the getter returns None.

Note: This property reflects the interval’s domain in the model (before the solve), not in the solution.

The value must be in the range IntervalMin to IntervalMax.

Example

import optalcp as cp

model = cp.Model()
task = model.interval_var(length=10, name="task")

task.start_max = 100
print(task.start_max)  # 100
property end_min: int | None

The minimum end time of the interval variable.

Gets or sets the minimum end time of the interval variable.

The initial value is set during construction by Model.interval_var(). If the interval is absent, the getter returns None.

Note: This property reflects the interval’s domain in the model (before the solve), not in the solution.

The value must be in the range IntervalMin to IntervalMax.

Example

import optalcp as cp

model = cp.Model()
task = model.interval_var(length=10, name="task")

print(task.end_min)  # 10 (start_min + length)

task.end_min = 20
print(task.end_min)  # 20
property end_max: int | None

The maximum end time of the interval variable.

Gets or sets the maximum end time of the interval variable.

The initial value is set during construction by Model.interval_var(). If the interval is absent, the getter returns None.

Note: This property reflects the interval’s domain in the model (before the solve), not in the solution.

The value must be in the range IntervalMin to IntervalMax.

Example

import optalcp as cp

model = cp.Model()
task = model.interval_var(length=10, name="task")

task.end_max = 100
print(task.end_max)  # 100
property length_min: int | None

The minimum length of the interval variable.

Gets or sets the minimum length of the interval variable.

The initial value is set during construction by Model.interval_var(). If the interval is absent, the getter returns None.

Note: This property reflects the interval’s domain in the model (before the solve), not in the solution.

The value must be in the range 0 to LengthMax.

Example

import optalcp as cp

model = cp.Model()
task = model.interval_var(length=10, name="task")

print(task.length_min)  # 10

task.length_min = 5
print(task.length_min)  # 5
property length_max: int | None

The maximum length of the interval variable.

Gets or sets the maximum length of the interval variable.

The initial value is set during construction by Model.interval_var(). If the interval is absent, the getter returns None.

Note: This property reflects the interval’s domain in the model (before the solve), not in the solution.

The value must be in the range 0 to LengthMax.

Example

import optalcp as cp

model = cp.Model()
task = model.interval_var(length=10, name="task")

print(task.length_max)  # 10

task.length_max = 20
print(task.length_max)  # 20
class optalcp.SequenceVar

Bases: ModelElement

Models a sequence (order) of interval variables.

A sequence variable represents an ordered arrangement of interval variables where no two intervals overlap. The sequence captures not just that the intervals don’t overlap, but also their relative ordering in the solution.

Sequence variables are created using Model.sequence_var() and are typically used with the SequenceVar.no_overlap() constraint to enforce non-overlapping with optional transition times between intervals.

The position of each interval in the sequence can be queried using Model.position() or IntervalVar.position(), which returns an integer expression representing the interval’s index in the final ordering (0-based). Absent intervals have an absent position.

See also

no_overlap(transitions: Iterable[Iterable[int]] | None = None) Constraint

Constrain the interval variables forming the sequence to not overlap.

Parameters:

transitions (Iterable[Iterable[int]] | None) – 2D square array of minimum transition distances between the intervals. The first index is the type (index) of the first interval in the sequence, the second index is the type (index) of the second interval in the sequence

Return type:

Constraint

Returns:

The no-overlap constraint.

Details

The no_overlap constraint makes sure that the intervals in the sequence do not overlap. That is, for every pair of interval variables x and y at least one of the following conditions must hold (in a solution):

1. Interval variable x is absent. This means that the interval is not present in the solution (not performed), so it cannot overlap with any other interval. Only optional interval variables can be absent. 2. Interval variable y is absent. 3. x ends before y starts, i.e. x.end() is less or equal to y.start(). 4. y ends before x starts, i.e. y.end() is less or equal to x.start().

In addition, if the transitions parameter is specified, then the cases 3 and 4 are further constrained by the minimum transition distance between the intervals:

  1. x.end() + transitions[x.type][y.type] is less or equal to y.start().

  2. y.end() + transitions[y.type][x.type] is less or equal to x.start().

where x.type and y.type are the types of the interval variables x and y as given in Model.sequence_var(). If types were not specified, then they are equal to the indices of the interval variables in the array passed to Model.sequence_var(). Transition times cannot be negative.

Note that transition times are enforced between every pair of interval variables, not only between direct neighbors.

The size of the 2D array transitions must be equal to the number of types of the interval variables.

This constraint is equivalent to Model.no_overlap() called with the sequence variable’s intervals and types.

Example

A worker must perform a set of tasks. Each task is characterized by:

  • length of the task (how long it takes to perform it),

  • location of the task (where it must be performed),

  • a time window earliest to deadline when the task must be performed.

There are three locations, 0, 1, and 2. The minimum travel times between the locations are given by a transition matrix transitions. Transition times are not symmetric. For example, it takes 10 minutes to travel from location 0 to location 1 but 15 minutes to travel back from location 1 to location 0.

We will model this problem using no_overlap constraint with transition times.

import optalcp as cp

# Travel times between locations:
transitions = [
  [ 0, 10, 10],
  [15,  0, 10],
  [ 5,  5,  0]
]
# Tasks to be scheduled:
tasks = [
  {"location": 0, "length": 20, "earliest": 0, "deadline": 100},
  {"location": 0, "length": 40, "earliest": 70, "deadline": 200},
  {"location": 1, "length": 10, "earliest": 0, "deadline": 200},
  {"location": 1, "length": 30, "earliest": 100, "deadline": 200},
  {"location": 1, "length": 10, "earliest": 0, "deadline": 150},
  {"location": 2, "length": 15, "earliest": 50, "deadline": 250},
  {"location": 2, "length": 10, "earliest": 20, "deadline": 60},
  {"location": 2, "length": 20, "earliest": 110, "deadline": 250},
]

model = cp.Model()

# From the array tasks create an array of interval variables:
task_vars = [model.interval_var(
  name=f"Task{i}", length=t["length"], start=(t["earliest"], None), end=(None, t["deadline"])
) for i, t in enumerate(tasks)]
# And an array of locations:
types = [t["location"] for t in tasks]

# Create the sequence variable for the tasks, location is the type:
sequence = model.sequence_var(task_vars, types)
# Tasks must not overlap and transitions must be respected:
sequence.no_overlap(transitions)

# Solve the model:
result = model.solve({'solutionLimit': 1})

Base Classes

class optalcp.ModelElement

Bases: object

The base class for all modeling objects.

Example

import optalcp as cp

model = cp.Model()
x = model.interval_var(length=10, name="x")
start = model.start(x)
result = model.solve()

Interval variable x and expression start are both instances of ModelElement. There are specialized descendant classes such as IntervalVar and IntExpr.

Any modeling object can be assigned a name using the ModelElement.name property.

property name: str | None

The name assigned to this model element.

The name is optional and primarily useful for debugging purposes. When set, it helps identify the element in solver logs, error messages, and when inspecting solutions.

Names can be assigned to any model element including variables, expressions, and constraints.

Example

import optalcp as cp

model = cp.Model()

# Name a variable at creation
task = model.interval_var(length=10, name="assembly")

# Or set name later
x = model.int_var(min=0, max=100)
x.name = "quantity"

print(task.name)  # "assembly"
print(x.name)     # "quantity"

Expressions

class optalcp.IntExpr

Bases: ModelElement

A class representing an integer expression in the model.

The expression may depend on the value of a variable (or variables), so the value of the expression is not known until a solution is found. The value must be in the range IntVarMin to IntVarMax.

Use standard arithmetic operators (+, -, *, //, %, unary -) and comparison operators (<, <=, ==, !=, >, >=).

Example

The following code creates two interval variables x and y and an integer expression makespan that is equal to the maximum of the end times of x and y (see Model.max2()):

model = cp.Model()
x = model.interval_var(length=10, name="x")
y = model.interval_var(length=20, name="y")
makespan = model.max2(x.end(), y.end())

Optional integer expressions

Underlying variables of an integer expression may be optional, i.e., they may or may not be present in a solution (for example, an optional task can be omitted entirely from the solution). In this case, the value of the integer expression is absent. The value absent means that the variable has no meaning; it does not exist in the solution.

Except IntExpr.guard() expression, any value of an integer expression that depends on an absent variable is also absent. As we don’t know the value of the expression before the solution is found, we call such expression optional.

Example

In the following model, there is an optional interval variable x and a non-optional interval variable y. We add a constraint that the end of x plus 10 must be less or equal to the start of y:

model = cp.Model()
x = model.interval_var(length=10, name="x", optional=True)
y = model.interval_var(length=20, name="y")
finish = x.end()
ready = finish + 10
begin = y.start()
precedes = ready <= begin
model.enforce(precedes)
result = model.solve()

In this model:

  • finish is an optional integer expression because it depends on an optional variable x.

  • The expression ready is optional for the same reason.

  • The expression begin is not optional because it depends only on a non-optional variable y.

  • Boolean expression precedes is also optional. Its value could be True, False or absent.

The expression precedes is turned into a constraint using Model.enforce(). Therefore, it cannot be False in the solution. However, it can still be absent. Therefore the constraint precedes can be satisfied in two ways:

1. Both x and y are present, x is before y, and the delay between them is at least 10. In this case, precedes is True. 2. x is absent and y is present. In this case, precedes is absent.

minimize() Objective

Creates a minimization objective for this expression.

Return type:

Objective

Returns:

An Objective that minimizes this expression.

Details

Creates an objective to minimize the value of this integer expression. A model can have at most one objective. New objective replaces the old one.

This is a fluent-style alternative to Model.minimize() that allows creating objectives directly from expressions.

Example

import optalcp as cp

model = cp.Model()
x = model.interval_var(length=10, name="x")
y = model.interval_var(length=20, name="y")

# Fluent style - minimize the end of y
y.end().minimize()

# Equivalent Model.minimize() style:
model.minimize(y.end())

See also

maximize() Objective

Creates a maximization objective for this expression.

Return type:

Objective

Returns:

An Objective that maximizes this expression.

Details

Creates an objective to maximize the value of this integer expression. A model can have at most one objective. New objective replaces the old one.

This is a fluent-style alternative to Model.maximize() that allows creating objectives directly from expressions.

Example

import optalcp as cp

model = cp.Model()
x = model.interval_var(start=(0, 100), length=10, name="x")

# Fluent style - maximize the start of x
x.start().maximize()

# Equivalent Model.maximize() style:
model.maximize(x.start())

See also

presence() BoolExpr

Returns an expression which is True if the expression is present and False when it is absent.

Return type:

BoolExpr

Returns:

The resulting Boolean expression

Details

The resulting expression is never absent.

Same as Model.presence().

guard(absent_value: int = 0) IntExpr

Creates an expression that replaces value absent by a constant.

Parameters:

absent_value (int) – The value to use when the expression is absent.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

The resulting expression is:

  • equal to the expression if the expression is present

  • and equal to absent_value otherwise (i.e. when the expression is absent).

The default value of absent_value is 0.

The resulting expression is never absent.

Same as Model.guard().

identity(rhs: IntExpr | int) Constraint

Constrains two expressions to be identical, including their presence status.

Parameters:

rhs (IntExpr | int) – The second integer expression.

Return type:

Constraint

Returns:

The constraint object

Details

Identity is different than equality. For example, if x is absent, then x == 0 is absent, but x.identity(0) is False.

Same as Model.identity().

in_range(lb: int, ub: int) BoolExpr

Creates Boolean expression lbthisub.

Parameters:
  • lb (int) – The lower bound of the range.

  • ub (int) – The upper bound of the range.

Return type:

BoolExpr

Returns:

The resulting Boolean expression

Details

If the expression has value absent, then the resulting expression has also value absent.

Use Model.enforce() to add this expression as a constraint to the model.

Same as Model.in_range().

abs() IntExpr

Creates an integer expression which is absolute value of the expression.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

If the expression has value absent, the resulting expression also has value absent.

Same as Model.abs().

min2(rhs: IntExpr | int) IntExpr

Creates an integer expression which is the minimum of the expression and arg.

Parameters:

rhs (IntExpr | int) – The second integer expression.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

If the expression or arg has value absent, then the resulting expression has also value absent.

Same as Model.min2(). See Model.min() for the n-ary minimum.

max2(rhs: IntExpr | int) IntExpr

Creates an integer expression which is the maximum of the expression and arg.

Parameters:

rhs (IntExpr | int) – The second integer expression.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

If the expression or arg has value absent, then the resulting expression has also value absent.

Same as Model.max2(). See Model.max() for n-ary maximum.

class optalcp.BoolExpr

Bases: IntExpr

A class that represents a boolean expression in the model. The expression may depend on one or more variables; therefore, its value may be unknown until a solution is found.

Example

For example, the following code creates two interval variables, x and y and a boolean expression precedes that is true if x ends before y starts, that is, if the end of x is less than or equal to the start of y:

Use standard comparison operators on expressions: x.end() <= y.start().

Boolean expressions can be used to create constraints using Model.enforce(). In the example above, we may require that precedes is true or absent:

model.enforce(precedes)

Optional boolean expressions

OptalCP is using 3-value logic: a boolean expression can be True, False or absent. Typically, the expression is absent only if one or more underlying variables are absent. The value absent means that the expression doesn’t have a meaning because one or more underlying variables are absent (not part of the solution).

Difference between constraints and boolean expressions

Boolean expressions can take arbitrary value (True, False, or absent) and can be combined into composed expressions (e.g., using BoolExpr.and_() or BoolExpr.or_()).

Constraints can only be True or absent (in a solution) and cannot be combined into composed expressions.

Some functions create constraints directly, e.g. Model.no_overlap(). It is not possible to combine constraints using logical operators like or_.

Example

Let’s consider a similar example to the one above but with an optional interval variables a and b:

model = cp.Model()
a = model.interval_var(length=10, name="a", optional=True)
b = model.interval_var(length=20, name="b", optional=True)
precedes = a.end() <= b.start()
model.enforce(precedes)
result = model.solve()

Adding a boolean expression as a constraint requires that the expression cannot be False in a solution. It could be absent though. Therefore, in our example, there are four kinds of solutions:

  1. Both a and b are present, and a ends before b starts.

  2. Only a is present, and b is absent.

  3. Only b is present, and a is absent.

  4. Both a and b are absent.

In case 1, the expression precedes is True. In all the other cases precedes is absent as at least one of the variables a and b is absent, and then precedes doesn’t have a meaning.

Boolean expressions as integer expressions

Class BoolExpr derives from IntExpr. Therefore, boolean expressions can be used as integer expressions. In this case, True is equal to 1, False is equal to 0, and absent remains absent.

enforce() None

Adds this boolean expression as a constraint to the model.

This method adds the boolean expression as a constraint to the model. It provides a fluent-style alternative to Model.enforce().

A constraint is satisfied if it is not False. In other words, a constraint is satisfied if it is True or absent.

A boolean expression that is not added as a constraint can have arbitrary value in a solution (True, False, or absent). Once added as a constraint, it can only be True or absent in the solution.

Example
import optalcp as cp

model = cp.Model()
x = model.int_var(min=0, max=10, name="x")
y = model.int_var(min=0, max=10, name="y")

# Enforce constraint using fluent style
(x + y <= 15).enforce()

# Equivalent to:
# model.enforce(x + y <= 15)

result = model.solve()

See also

not_() BoolExpr

Returns negation of the expression.

Return type:

BoolExpr

Returns:

The resulting Boolean expression

Details

If the expression has value absent then the resulting expression has also value absent.

Same as Model.not_().

or_(rhs: BoolExpr | bool) BoolExpr

Returns logical _OR_ of the expression and arg.

Parameters:

rhs (BoolExpr | bool) – The second boolean expression.

Return type:

BoolExpr

Returns:

The resulting Boolean expression

Details

If the expression or arg has value absent then the resulting expression has also value absent.

Same as Model.or_().

and_(rhs: BoolExpr | bool) BoolExpr

Returns logical _AND_ of the expression and arg.

Parameters:

rhs (BoolExpr | bool) – The second boolean expression.

Return type:

BoolExpr

Returns:

The resulting Boolean expression

Details

If the expression or arg has value absent, then the resulting expression has also value absent.

Same as Model.and_().

implies(rhs: BoolExpr | bool) BoolExpr

Returns implication between the expression and arg.

Parameters:

rhs (BoolExpr | bool) – The second boolean expression.

Return type:

BoolExpr

Returns:

The resulting Boolean expression

Details

If the expression or arg has value absent, then the resulting expression has also value absent.

Same as Model.implies().

class optalcp.CumulExpr

Bases: ModelElement

Cumulative expression.

Cumulative expression represents resource usage over time. The resource could be a machine, a group of workers, a material, or anything of a limited capacity. The resource usage is not known in advance as it depends on the variables of the problem. Cumulative expressions allow us to model the resource usage and constrain it.

Basic cumulative expressions are:

  • *Pulse*: the resource is used over an interval of time. For example, a pulse can represent a task requiring a certain number of workers during its execution. At the beginning of the interval, the resource usage increases by a given amount, and at the end of the interval, the resource usage decreases by the same amount. Pulse can be created by function Model.pulse() or IntervalVar.pulse().

  • *Step*: a given amount of resource is consumed or produced at a specified time (e.g., at the start of an interval variable). Steps may represent an inventory of a material that is consumed or produced by some tasks (a reservoir). Steps can be created by functions Model.step_at_start(), IntervalVar.step_at_start(), Model.step_at_end(), IntervalVar.step_at_end(). and Model.step_at().

Cumulative expressions can be combined using operators (+, -, unary -) and Model.sum(). The resulting cumulative expression represents a sum of the resource usage of the combined expressions.

Cumulative expressions can be constrained using comparison operators (<=, >=) to specify the minimum and maximum allowed resource usage.

Limitations:

  • Pulse-based and step-based cumulative expressions cannot be mixed.

  • Pulses cannot have negative height. Use - and unary - only with step-based expressions.

class optalcp.Constraint

Bases: ModelElement

Represents a constraint in the model.

A constraint is a condition that must be satisfied by a valid solution. Constraints are created by calling constraint-creating methods on the model or on expressions.

Important: Constraints are automatically registered with the model when created. There is no need to explicitly add them using Model.enforce() or similar methods.

Note that comparison operators on integer expressions (like x <= 10) return BoolExpr, not Constraint. Boolean expressions must be explicitly enforced using Model.enforce() or BoolExpr.enforce().

Unlike BoolExpr, constraints cannot be combined using logical operators like Model.and_()/Model.or_() or &/|.

Common ways to create constraints:

model = cp.Model()

# Scheduling constraint - automatically registered
tasks = [model.interval_var(length=5, name=f"t_{i}") for i in range(3)]
model.no_overlap(tasks)

# Precedence constraint - automatically registered
task1 = model.interval_var(length=10, name="task1")
task2 = model.interval_var(length=10, name="task2")
task1.end_before_start(task2)

# Cumulative constraint - use model.enforce() for clarity
resource = model.sum(model.pulse(t, 1) for t in tasks)
model.enforce(resource <= 2)

See also

  • Model.enforce() for enforcing boolean expressions as constraints.

  • BoolExpr for boolean expressions that can also be enforced as constraints.

class optalcp.Objective

Bases: ModelElement

Represents an optimization objective in the model.

An objective specifies what value should be minimized or maximized when solving the model. Objectives are created by calling Model.minimize() or Model.maximize(), or by using the fluent methods IntExpr.minimize() or IntExpr.maximize().

A model can have at most one objective.

model = cp.Model()
x = model.interval_var(length=10, name="x")
y = model.interval_var(length=20, name="y")

# Create objective using Model.minimize() - automatically registered:
model.minimize(y.end())

# Or using fluent style on expressions - automatically registered:
y.end().minimize()

See also

Functions

class optalcp.IntStepFunction

Bases: ModelElement

Integer step function.

Integer step function is a piecewise constant function defined on integer values in range IntVarMin to IntVarMax. The function can be created by Model.step_function().

Step functions can be used in the following ways:

integral(interval: IntervalVar) IntExpr

Computes sum of values of the step function over the interval interval.

Parameters:

interval (IntervalVar) – The interval variable.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

The sum is computed over all integer time points from interval.start() to interval.end()-1 inclusive. In other words, the sum includes the function value at the start time but excludes the value at the end time (half-open interval). If the interval variable has zero length, then the result is 0. If the interval variable is absent, then the result is absent.

Requirement: The step function func must be non-negative.

See also

eval(arg: IntExpr | int) IntExpr

Evaluates the step function at a given point.

Parameters:

arg (IntExpr | int) – The point at which to evaluate the step function.

Return type:

IntExpr

Returns:

The resulting integer expression

Details

The result is the value of the step function at the point arg. If the value of arg is absent, then the result is also absent.

By constraining the returned value, it is possible to limit arg to be only within certain segments of the segmented function. In particular, functions Model.forbid_start() and Model.forbid_end() work that way.

See also

Solving

class optalcp.Solver

Bases: object

Provides asynchronous communication with the solver subprocess.

Unlike function Model.solve(), Solver allows the user to process individual events during the solve and to stop the solver at any time. If you’re interested in the final result only, use Model.solve() instead.

To solve a model, create a new Solver object and call its method Solver.solve().

The Python Solver uses callback properties to handle events:

The solver output (log, trace, and warnings) is printed to console by default. It can be redirected to a file or suppressed using the Parameters.printLog parameter.

Example

In the following example, we run a solver asynchronously. We set up an on_solution callback to print the objective value of the solution and the value of interval variable x. After finding the first solution, we request the solver to stop. We also set up an on_summary callback to print statistics about the solve.

import optalcp as cp

model = cp.Model()
x = model.interval_var(length=10, name="x")
model.minimize(x.end())

# Create a new solver:
solver = cp.Solver()

# Define solution handler:
async def handle_solution(event: cp.SolutionEvent):
    solution = event.solution
    print(f"At time {event.solve_time:.2f}, solution found with objective {solution.get_objective()}")
    # Print value of interval variable x:
    if solution.is_absent(x):
        print("  Interval variable x is absent")
    else:
        print(f"  Interval variable x: [{solution.get_start(x)} -- {solution.get_end(x)}]")
    # Request the solver to stop as soon as possible:
    await solver.stop("We are happy with the first solution found.")

# Define summary handler:
def handle_summary(summary: cp.SolveSummary):
    print(f"Total duration of solve: {summary.duration}")
    print(f"Number of branches: {summary.nb_branches}")

# Set callbacks:
solver.on_solution = handle_solution
solver.on_summary = handle_summary

# Solve (async):
result = await solver.solve(model, {'timeLimit': 60})
print("All done")
property on_log: Callable[[str], None] | Callable[[str], Awaitable[None]] | None

Callback for log messages from the solver.

The callback is called for each log message, after the message is written to the output stream (see Parameters.printLog). The default is no callback.

The callback receives one argument:

  • msg (str): The log message text.

The callback can be either synchronous or asynchronous. If the callback raises an exception, the solve is aborted with that error.

Note: This property cannot be changed while a solve is in progress. Attempting to set it during an active solve raises an error.

solver = cp.Solver()

# Synchronous callback
solver.on_log = lambda msg: my_logger.info(msg)

# Or with a named function
def log_handler(msg: str) -> None:
    print(f"LOG: {msg}")

solver.on_log = log_handler

# Asynchronous callback
async def async_log_handler(msg: str) -> None:
    await async_logger.info(msg)

solver.on_log = async_log_handler

The amount of log messages and their periodicity can be controlled by Parameters.logLevel and Parameters.logPeriod.

See also

property on_warning: Callable[[str], None] | Callable[[str], Awaitable[None]] | None

Callback for warning messages from the solver.

The callback is called for each warning message, after the message is written to the output stream (see Parameters.printLog). The default is no callback.

The callback receives one argument:

  • msg (str): The warning message text.

The callback can be either synchronous or asynchronous. If the callback raises an exception, the solve is aborted with that error.

Note: This property cannot be changed while a solve is in progress. Attempting to set it during an active solve raises an error.

solver = cp.Solver()

# Synchronous callback
solver.on_warning = lambda msg: warnings.warn(msg)

# Asynchronous callback
async def async_warning_handler(msg: str) -> None:
    await async_logger.warning(msg)

solver.on_warning = async_warning_handler

The amount of warning messages can be configured using Parameters.warningLevel.

See also

property on_error: Callable[[str], None] | Callable[[str], Awaitable[None]] | None

Callback for error messages from the solver.

The callback is called for each error message. The default is no callback.

The callback receives one argument:

  • msg (str): The error message text.

The callback can be either synchronous or asynchronous. If the callback raises an exception, the solve is aborted with that error.

Note: This property cannot be changed while a solve is in progress. Attempting to set it during an active solve raises an error.

solver = cp.Solver()

# Synchronous callback
solver.on_error = lambda msg: print(f"ERROR: {msg}", file=sys.stderr)

# Asynchronous callback
async def async_error_handler(msg: str) -> None:
    await async_logger.error(msg)

solver.on_error = async_error_handler

An error message indicates that the solve is closing. However, other messages (log, warning, solution) may still arrive after the error.

See also

property on_solution: Callable[[SolutionEvent], None] | Callable[[SolutionEvent], Awaitable[None]] | None

Callback for solution events from the solver.

The callback is called each time the solver finds a new solution. The default is no callback.

The callback receives one argument:

  • event (SolutionEvent): An object with the following fields:
    • solution (Solution): The solution object with variable values.

    • solve_time (float): Time when the solution was found (seconds since solve start).

    • valid (bool | None): Solution verification result if enabled, None otherwise.

The callback can be either synchronous or asynchronous. If the callback raises an exception, the solve is aborted with that error.

Note: This property cannot be changed while a solve is in progress. Attempting to set it during an active solve raises an error.

def handle_solution(event: cp.SolutionEvent) -> None:
    sol = event.solution
    time = event.solve_time
    print(f"Solution found at {time:.2f}s, objective={sol.get_objective()}")

solver = cp.Solver()
solver.on_solution = handle_solution

# Asynchronous callback
async def async_handle_solution(event: cp.SolutionEvent) -> None:
    sol = event.solution
    await save_to_database(sol)

solver.on_solution = async_handle_solution

See also

property on_objective_bound: Callable[[ObjectiveBoundEntry], None] | Callable[[ObjectiveBoundEntry], Awaitable[None]] | None

Callback for objective bound events from the solver.

The callback is called when the solver improves the bound on the objective (lower bound for minimization, upper bound for maximization). The default is no callback.

The callback receives one argument:

  • event (ObjectiveBoundEntry): An object with the following fields:
    • value (float): The new bound value.

    • solve_time (float): Time when the bound was proved (seconds since solve start).

The callback can be either synchronous or asynchronous. If the callback raises an exception, the solve is aborted with that error.

Note: This property cannot be changed while a solve is in progress. Attempting to set it during an active solve raises an error.

solver = cp.Solver()

# Synchronous callback
solver.on_objective_bound = lambda event: print(f"Bound: {event.value}")

# Asynchronous callback
async def async_bound_handler(event: cp.ObjectiveBoundEntry) -> None:
    await update_dashboard(event.value)

solver.on_objective_bound = async_bound_handler

See also

property on_summary: Callable[[SolveSummary], None] | Callable[[SolveSummary], Awaitable[None]] | None

Callback for solve completion event.

The callback is called once when the solve completes, providing final statistics. The default is no callback.

The callback receives one argument:

  • summary (SolveSummary): Solve statistics with properties including:
    • nb_solutions (int): Number of solutions found.

    • duration (float): Total solve time in seconds.

    • nb_branches (int): Number of branches explored.

    • objective (float | None): Best objective value, or None if no solution found.

    • Plus many other statistics (see SolveSummary).

The callback can be either synchronous or asynchronous. If the callback raises an exception, the solve is aborted with that error.

Note: This property cannot be changed while a solve is in progress. Attempting to set it during an active solve raises an error.

def handle_summary(summary: cp.SolveSummary) -> None:
    print(f"Solve completed: {summary.nb_solutions} solutions")
    print(f"Time: {summary.duration:.2f}s")
    if summary.objective is not None:
        print(f"Best objective: {summary.objective}")

solver = cp.Solver()
solver.on_summary = handle_summary

# Asynchronous callback
async def async_handle_summary(summary: cp.SolveSummary) -> None:
    await save_stats_to_db(summary)

solver.on_summary = async_handle_summary

See also

static find_solver(parameters: Parameters | None = None) str

Find path to the optalcp binary.

Parameters:

parameters (Parameters | None) – Parameters object that may contain the solver path or URL

Return type:

str

Returns:

The path to the solver executable or WebSocket URL

Details

This static method is used to find the solver for Model.solve(). Usually, there’s no need to call this method directly. However, it could be used to check what solver would be used.

The method works as follows:

  • If parameters.solver is set, its value is returned (path or URL).

  • If the OPTALCP_SOLVER environment variable is set, then it is used.

  • If pip package optalcp-bin is installed then it is searched for the optalcp executable.

  • If pip package optalcp-bin-academic is installed then it is searched too.

  • If pip package optalcp-bin-preview is installed then it is searched too.

  • Finally, if nothing from the above works, the method assumes that optalcp is in the PATH.

import optalcp as cp

# Check what solver would be used
solver = cp.Solver.find_solver()
print("Solver:", solver)

# Or check with custom parameters
custom = cp.Solver.find_solver({'solver': '/custom/path/optalcp'})

See also

async solve(model: Model, params: Parameters | None = None, warm_start: Solution | None = None) SolveResult

Solves a model with the specified parameters.

Parameters:
  • model (Model) – The model to solve

  • params (Parameters | None) – The parameters for the solver

  • warm_start (Solution | None) – An initial solution to start the solver with

Return type:

SolveResult

Returns:

The result of the solve when finished.

Details

The solving process runs asynchronously. Use await to wait for the solver to finish. During the solve, the solver emits events that can be intercepted using callback properties like Solver.on_solution, Solver.on_log, etc.

Communication with the solver subprocess happens through the event loop. The user code must yield control (using await or waiting for an event) for the solver to receive commands and send updates.

Warm start and external solutions

If the warm_start parameter is specified, the solver will start with the given solution. The solution must be compatible with the model; otherwise an error is raised. The solver will take advantage of the solution to speed up the search: it will search only for better solutions (if it is a minimization or maximization problem). The solver may try to improve the provided solution by Large Neighborhood Search.

There are two ways to pass a solution to the solver: using warm_start parameter and using function Solver.send_solution(). The difference is that warm_start is guaranteed to be used by the solver before the solve starts. On the other hand, send_solution can be called at any time during the solve.

Parameter Parameters.lnsUseWarmStartOnly controls whether the solver should only use the warm start solution (and not search for other initial solutions). If no warm start is provided, the solver searches for its own initial solution as usual.

stop(reason: str = 'User requested') None

Requests the solver to stop as soon as possible.

Parameters:

reason (str) – The reason why to stop. The reason will appear in the log

Details

This method only initiates the stop; it returns immediately without waiting for the solver to actually stop. The solver will stop as soon as possible and will send a summary event. However, other events may be sent before the summary event (e.g., another solution found or a log message).

Requesting a stop on a solver that has already stopped has no effect.

Example

In the following example, we issue a stop command 1 minute after the first solution is found.

import optalcp as cp
import threading

solver = cp.Solver()
timer_started = False

def on_solution(event):
    global timer_started
    # We just found a solution. Set a timeout if there isn't any.
    if not timer_started:
        timer_started = True
        # Register a function to be called after 60 seconds:
        def stop_solver():
            print("Requesting solver to stop")
            solver.stop("Stop because I said so!")
        timer = threading.Timer(60.0, stop_solver)  # The timeout is 60 seconds
        timer.start()

solver.on_solution = on_solution
result = await solver.solve(model, {'timeLimit': 300})
async send_solution(solution: Solution) None

Send an external solution to the solver.

Parameters:

solution (Solution) – The solution to send. It must be compatible with the model; otherwise, an error is raised

Details

This function can be used to send an external solution to the solver, e.g. found by another solver, a heuristic, or a user. The solver will take advantage of the solution to speed up the search: it will search only for better solutions (if it is a minimization or maximization problem). The solver may try to improve the provided solution by Large Neighborhood Search.

The solution does not have to be better than the current best solution found by the solver. It is up to the solver whether or not it will use the solution in this case.

Sending a solution to a solver that has already stopped has no effect.

The solution is sent to the solver asynchronously. Unless parameter Parameters.logLevel is set to 0, the solver will log a message when it receives the solution.

async to_text(model: Model, parameters: Parameters | None = None, warm_start: Solution | None = None) str

Converts a model to text format (async version).

Parameters:
  • model (Model) – The model to convert

  • parameters (Parameters | None) – Optional solver parameters

  • warm_start (Solution | None) – Optional initial solution to include

Return type:

str

Returns:

Text representation of the model.

Details

Async version of Model.to_text(). This method communicates with the solver process to generate the text output.

The output is human-readable and similar to the IBM CP Optimizer file format.

import asyncio
import optalcp as cp

async def export_model():
    model = cp.Model()
    x = model.interval_var(length=10, name="task_x")
    model.minimize(x.end())

    solver = cp.Solver()
    text = await solver.to_txt(model)
    return text

text = asyncio.run(export_model())
print(text)

See also

async to_js(model: Model, parameters: Parameters | None = None, warm_start: Solution | None = None) str

Converts a model to JavaScript code (async version).

Parameters:
  • model (Model) – The model to convert

  • parameters (Parameters | None) – Optional solver parameters (included in generated code)

  • warm_start (Solution | None) – Optional initial solution to include

Return type:

str

Returns:

JavaScript code representing the model.

Details

Async version of Model.to_js(). This method communicates with the solver process to generate the JavaScript output.

The output is human-readable, executable with Node.js, and can be stored in a file.

This feature is experimental and the result is not guaranteed to be valid in all cases.

import asyncio
import optalcp as cp

async def export_model():
    model = cp.Model()
    x = model.interval_var(length=10, name="task_x")
    model.minimize(x.end())

    solver = cp.Solver()
    js_code = await solver.to_js(model)
    return js_code

js_code = asyncio.run(export_model())
print(js_code)

See also

class optalcp.Parameters

Bases: TypedDict

Parameters specify how the solver should behave. For example, the number of workers (threads) to use, the time limit, etc.

Parameters can be passed to the solver functions Model.solve() and Solver.solve().

Example

In the following example, we are using the TimeLimit parameter to specify that the solver should stop after 5 minutes. We also specify that the solver should use 4 threads. Finally, we specify that the solver should use FDS search (in all threads).

import optalcp as cp

params: cp.Parameters = {
    'timeLimit': 300,  # In seconds, i.e. 5 minutes
    'nbWorkers': 4,    # Use 4 threads
    'searchType': 'FDS',
}
result = my_model.solve(params)

Worker-specific parameters

Some parameters can be specified differently for each worker. For example, some workers may use LNS search while others use FDS search. To specify worker-specific parameters, use the workers parameter and pass an array of WorkerParameters.

Not all parameters can be specified per worker. For example, TimeLimit is a global parameter. See WorkerParameters for the list of parameters that can be specified per worker.

If a parameter is not set specifically for a worker, the global value is used.

Example

In the following example, we are going to use 4 workers; two of them will run FDS search and the remaining two will run LNS search. In addition, workers that use FDS search will use increased propagation levels.

import optalcp as cp

# Parameters for a worker that uses FDS search.
# FDS works best with increased propagation levels, so set them:
fds_worker: cp.WorkerParameters = {
    'searchType': 'FDS',
    'noOverlapPropagationLevel': 4,
    'cumulPropagationLevel': 3,
    'reservoirPropagationLevel': 2,
}
# Global parameters:
params: cp.Parameters = {
    'timeLimit': 60,      # In seconds, i.e. 1 minute
    'searchType': 'LNS',  # The default search type. It is not necessary, as "LNS" is the default value.
    'nbWorkers': 4,       # Use 4 threads
    # The first two workers will use FDS search.
    # The remaining two workers will use the defaults, i.e., LNS search with default propagation levels.
    'workers': [fds_worker, fds_worker],
}
result = my_model.solve(params)

See also

color: Literal['Never', 'Auto', 'Always']

Whether to colorize output to the terminal

This parameter controls when terminal output is colorized. Possible values are:

  • Never: don’t colorize the output.

  • Auto: colorize if the output is a supported terminal.

  • Always: always colorize the output.

The default value is Auto.

nbWorkers: int

Number of threads dedicated to search

When this parameter is 0 (the default), the number of workers is determined the following way:

  • If environment variable OPTALCP_NB_WORKERS is set, its value is used.

  • Otherwise, all available cores are used.

The parameter takes an integer value.

The default value is 0.

preset: Literal['Auto', 'Default', 'Large']

Preset configuration for solver parameters

Presets provide reasonable default values for multiple solver parameters at once. Instead of manually tuning individual parameters, you can select a preset that matches your problem characteristics. The solver will then configure search strategies and propagation levels appropriately.

Available presets:

  • Auto: The solver automatically selects a preset based on problem size (the default). Problems with more than 100,000 variables use Large, otherwise Default.

  • Default: Balanced configuration for most problems. Uses maximum propagation levels and distributes workers across different search strategies: half use LNS, 3/8 use FDS, and the rest use FDSDual. This provides a good mix of exploration and exploitation.

  • Large: Optimized for big problems with more than 100,000 variables. Uses minimum propagation to reduce overhead, and all workers use LNS search. This trades propagation strength for scalability.

Parameters affected by presets:

The preset sets default values for the following parameters:

When you explicitly set any of these parameters, your value takes precedence over the preset’s default. This allows you to use a preset as a starting point and fine-tune specific parameters as needed.

When to use presets:

Presets are a good starting point for most problems. They are not guaranteed to be optimal for your specific problem, but they provide reasonable defaults that work well in practice. If you find that the default preset is not working well for your problem, consider:

  • Trying the Large preset for very big problems, even if they have fewer than 100,000 variables

  • Explicitly setting Parameters.searchType to use a specific search strategy

  • Adjusting propagation levels based on your problem structure

import optalcp as cp

model = cp.Model()
# ... build your model ...

# Use automatic preset selection
result = model.solve()

# Or explicitly select a preset for a large problem
result = model.solve(preset="Large")

# Or use Default preset but override search type
result = model.solve(preset="Default", searchType="FDS")

See also

searchType: Literal['Auto', 'LNS', 'FDS', 'FDSDual', 'SetTimes', 'FDSLB']

Type of search to use

This parameter controls which search algorithm the solver uses. Different search types have different strengths:

  • Auto: Automatically determined based on the Parameters.preset (the default). With the Default preset, workers are distributed across LNS, FDS, and FDSDual. With the Large preset, all workers use LNS.

  • LNS: Large Neighborhood Search. Starts from an initial solution and iteratively improves it by relaxing and re-optimizing parts of the solution. Good for finding high-quality solutions quickly, especially on large problems. Works best when a good initial solution can be found.

  • FDS: Failure-Directed Search. A systematic search that learns from failures to guide exploration. Uses restarts with no-good learning. Often effective at proving optimality and works well with strong propagation.

  • FDSDual: Failure-Directed Search working on objective bounds. Similar to FDS but focuses on proving bounds on the objective value. Useful for optimization problems where you want to know how far from optimal your solutions are.

  • SetTimes: Depth-first set-times search (not restarted). A simple chronological search that assigns start times in order. Can be effective for tightly constrained problems but generally less robust than other methods.

Interaction with presets:

When searchType is set to Auto, the actual search type is determined by the Parameters.preset:

  • Default preset: Distributes workers across different search types. Half use LNS, 3/8 use FDS, and the rest use FDSDual. This portfolio approach provides robustness across different problem types.

  • Large preset: All workers use LNS. For very large problems, the overhead of systematic search methods like FDS becomes prohibitive, so LNS is used exclusively.

If you explicitly set searchType to a specific value (not Auto), that value is used regardless of the preset.

import optalcp as cp

model = cp.Model()
# ... build your model ...

# Let the preset decide (default behavior)
result = model.solve()

# Or explicitly use FDS for systematic search
result = model.solve(searchType="FDS")

# Or use LNS for quick solutions on large problems
result = model.solve(searchType="LNS")

See also

randomSeed: int

Random seed

The solver breaks ties randomly using a pseudorandom number generator. This parameter sets the seed of the generator.

Note that when Parameters.nbWorkers is more than 1 then there is also another source of randomness: the time it takes for a message to pass from one worker to another. Therefore with 1 worker the solver is deterministic (random behavior depends only on random seed). With more workers the solver is not deterministic.

Even with the same random seed, the solver may behave differently on different platforms. This can be due to different implementations of certain functions such as std::sort.

The parameter takes an integer value.

The default value is 1.

logLevel: int

Level of the log

This parameter controls the amount of text the solver writes on standard output. The solver is completely silent when this option is set to 0.

The parameter takes an integer value in range 0..3.

The default value is 2.

warningLevel: int

Level of warnings

This parameter controls the types of warnings the solver emits. When this parameter is set to 0 then no warnings are emitted.

The parameter takes an integer value in range 0..3.

The default value is 2.

logPeriod: float

How often to print log messages (in seconds)

When Parameters.logLevel &ge; 2 then solver writes a log message every logPeriod seconds. The log message contains the current statistics about the solve: number of branches, number of fails, memory used, etc.

The parameter takes a floating point value in range 0.01..Infinity.

The default value is 10.

verifySolutions: bool

When on, the correctness of solutions is verified

Verification is an independent algorithm that checks whether all constraints in the model are satisfied (or absent), and that objective value was computed correctly. Verification is a somewhat redundant process as all solutions should be correct. Its purpose is to double-check and detect bugs in the solver.

The default value is False.

verifyExternalSolutions: bool

Whether to verify correctness of external solutions

External solutions can be passed to the solver as a warm start via Model.solve(), or using Solver.send_solution() during the search. Normally, all external solutions are checked before they are used. However, the check may be time consuming, especially if too many external solutions are sent simultaneously. This parameter allows to turn the check off.

The default value is True.

allocationBlockSize: int

The minimal amount of memory in kB for a single allocation

The solver allocates memory in blocks. This parameter sets the minimal size of a block. Larger blocks mean a higher risk of wasting memory. However, larger blocks may also lead to better performance, particularly when the size matches the page size supported by the operating system.

The value of this parameter must be a power of 2.

The default value of 2048 means 2MB, which means that up to ~12MB can be wasted per worker in the worst case.

The parameter takes an integer value in range 4..1073741824.

The default value is 2048.

processExitTimeout: float

Timeout for solver process to exit after finishing

After the solver finishes, wait up to this many seconds for the process to exit. If it doesn’t exit in time, it is silently killed.

The parameter takes a floating point value in range 0.0..Infinity.

The default value is 3.

timeLimit: float

Wall clock limit for execution in seconds

Caps the total wall-clock time spent by the solver. The timer starts as soon as the solve begins, and it includes presolve, search, and verification. When the limit is reached, all workers stop cooperatively. Leave it at the default Infinity to run without a time bound.

The parameter takes a floating point value in range 0.0..Infinity.

The default value is Infinity.

solutionLimit: int

Stop the search after the given number of solutions

Terminates the solve after the specified number of solutions have been found and reported.

Automatic behavior (value 0):

When set to 0 (the default), the limit is determined automatically based on the problem type:

  • Decision problems (no objective): The solver stops after finding the first solution. This is usually what you want for feasibility problems.

  • Optimization problems: No limit is applied. The solver continues searching for better solutions until it proves optimality, hits another limit (like Parameters.timeLimit), or is stopped manually.

Explicit values:

You can set an explicit limit to control solution enumeration:

  • 1: Stop after the first solution. Useful when you just need any feasible solution quickly, even for optimization problems.

  • N > 1: Find up to N solutions. Useful for:
    • Generating multiple alternative solutions for warm starts

    • Enumerating all solutions to small problems

    • Finding a diverse set of solutions for analysis

Note on optimization problems:

For optimization problems, only improving solutions are counted. If you set solutionLimit=5, the solver will stop after finding 5 solutions, each better than the previous. Non-improving solutions (which can occur during the search) are not counted toward the limit.

Note on LNS and decision problems:

When using LNS search (see Parameters.searchType) on decision problems (no objective), be aware that LNS may report duplicate solutions. LNS works by iteratively improving a solution, and for decision problems without an objective to guide the search, it may find the same solution multiple times. If you need unique solutions, consider using FDS search instead, or filter duplicates in your application code.

import optalcp as cp

model = cp.Model()
# ... build your model ...

# Automatic behavior (default)
# - Decision problem: stops after 1 solution
# - Optimization: no limit
result = model.solve()

# Stop after first solution (useful for quick feasibility check)
result = model.solve(solutionLimit=1)

# Find up to 10 solutions for warm starts
result = model.solve(solutionLimit=10)

See also

absoluteGapTolerance: float

Stop the search when the gap is below the tolerance

The search is stopped if the absolute difference between the current solution value and current lower/upper bound is not bigger than the specified value.

This parameter works together with Parameters.relativeGapTolerance as an OR condition: the search stops when either the absolute gap or the relative gap is within tolerance.

The parameter takes a floating point value.

The default value is 0.

relativeGapTolerance: float

Stop the search when the gap is below the tolerance

The search is stopped if the relative difference between the current solution value and current lower/upper bound is not bigger than the specified value.

This parameter works together with Parameters.absoluteGapTolerance as an OR condition: the search stops when either the absolute gap or the relative gap is within tolerance.

The parameter takes a floating point value.

The default value is 0.0001.

noOverlapPropagationLevel: int

How much to propagate noOverlap constraints

This parameter controls the amount of propagation done for noOverlap constraints. Higher levels use more sophisticated algorithms that can detect more infeasibilities and prune more values from domains, but at the cost of increased computation time.

Propagation levels:

  • Level 1: Basic timetable propagation only

  • Level 2: Adds detectable precedences algorithm

  • Level 3: Adds edge-finding reasoning

  • Level 4: Maximum propagation with all available algorithms

Automatic selection (level 0):

When set to 0 (the default), the propagation level is determined automatically based on the Parameters.preset:

  • Default preset: Uses level 4 (maximum propagation)

  • Large preset: Uses level 1 (minimum propagation for scalability)

Performance considerations:

More propagation doesn’t necessarily mean better overall performance. The trade-off depends on your problem:

  • Dense scheduling problems with many overlapping intervals often benefit from higher propagation levels because the extra pruning reduces the search space significantly.

  • Sparse problems or very large problems may perform better with lower propagation levels because the overhead of sophisticated algorithms outweighs the benefit.

  • FDS search (see Parameters.searchType) typically benefits from higher propagation levels because it relies on strong propagation to guide the search.

If you’re unsure, start with the automatic selection (level 0) and let the preset choose. You can then experiment with explicit levels if needed.

import optalcp as cp

model = cp.Model()
# ... build your model with noOverlap constraints ...

# Let the preset decide (default)
result = model.solve()

# Or use maximum propagation for dense problems
result = model.solve(noOverlapPropagationLevel=4)

# Or use minimum propagation for very large problems
result = model.solve(noOverlapPropagationLevel=1)

See also

cumulPropagationLevel: int

How much to propagate constraints on cumul functions

This parameter controls the amount of propagation done for cumulative constraints (e.g., cumul <= limit) when used with a sum of Model.pulse() pulses.

Higher levels use more sophisticated algorithms that can detect more infeasibilities and prune more values from domains, but at the cost of increased computation time.

Propagation levels:

  • Level 1: Basic timetable propagation

  • Level 2: Adds time-table edge-finding

  • Level 3: Maximum propagation with all available algorithms

Automatic selection (level 0):

When set to 0 (the default), the propagation level is determined automatically based on the Parameters.preset:

  • Default preset: Uses level 3 (maximum propagation)

  • Large preset: Uses level 1 (minimum propagation for scalability)

Performance considerations:

More propagation doesn’t necessarily mean better overall performance. The trade-off depends on your problem:

  • Resource-constrained problems with tight capacity limits often benefit from higher propagation levels because cumulative reasoning can prune many infeasible assignments.

  • Problems with loose resource constraints may not benefit much from higher levels because the extra computation doesn’t lead to significant pruning.

  • Very large problems may perform better with lower propagation levels because the overhead becomes prohibitive.

  • FDS search (see Parameters.searchType) typically benefits from higher propagation levels.

If you’re unsure, start with the automatic selection (level 0) and let the preset choose.

import optalcp as cp

model = cp.Model()
# ... build your model with cumulative constraints ...

# Let the preset decide (default)
result = model.solve()

# Or use maximum propagation for resource-constrained problems
result = model.solve(cumulPropagationLevel=3)

# Or use minimum propagation for very large problems
result = model.solve(cumulPropagationLevel=1)

See also

reservoirPropagationLevel: int

How much to propagate constraints on cumul functions

This parameter controls the amount of propagation done for cumulative constraints (e.g., cumul <= limit, cumul >= limit) when used together with steps (Model.step_at_start(), Model.step_at_end(), Model.step_at()). The bigger the value, the more algorithms are used for propagation. It means that more time is spent by the propagation, and possibly more values are removed from domains. More propagation doesn’t necessarily mean better performance. FDS search (see Parameters.searchType) usually benefits from higher propagation levels.

The parameter takes an integer value in range 1..2.

The default value is 1.

positionPropagationLevel: int

How much to propagate position expressions on noOverlap constraints

This parameter controls the amount of propagation done for position expressions on noOverlap constraints. The bigger the value, the more algorithms are used for propagation. It means that more time is spent by the propagation, and possibly more values are removed from domains. However, more propagation doesn’t necessarily mean better performance. FDS search (see Parameters.searchType) usually benefits from higher propagation levels.

The parameter takes an integer value in range 1..3.

The default value is 2.

integralPropagationLevel: int

How much to propagate integral expression

This parameter controls the amount of propagation done for Model.integral() expressions. In particular, it controls whether the propagation also affects the minimum and the maximum length of the associated interval variable:

  • 1: The length is updated only once during initial constraint propagation.

  • 2: The length is updated every time the expression is propagated.

The parameter takes an integer value in range 1..2.

The default value is 1.

usePrecedenceEnergy: int

Whether to use precedence energy propagation algorithm

Precedence energy algorithm improves propagation of precedence constraints when an interval has multiple predecessors (or successors) which use the same resource (noOverlap or cumulative constraint). In this case, the predecessors (or successors) may be in disjunction. Precedence energy algorithm can leverage this information and propagate the precedence constraint more aggressively.

The parameter takes an integer value: 0 to disable, 1 to enable.

The default value is 0.

searchTraceLevel: int

Level of search trace

This parameter is available only in the development edition of the solver.

When set to a value bigger than zero, the solver prints a trace of the search. The trace contains information about every choice taken by the solver. The higher the value, the more information is printed.

The parameter takes an integer value in range 0..5.

The default value is 0.

propagationTraceLevel: int

Level of propagation trace

This parameter is available only in the development edition of the solver.

When set to a value bigger than zero, the solver prints a trace of the propagation, that is a line for every domain change. The higher the value, the more information is printed.

The parameter takes an integer value in range 0..5.

The default value is 0.

infoTraceLevel: int

Level of information trace

This parameter is available only in the development edition of the solver.

When set to a value bigger than zero, the solver prints various high-level information. The higher the value, the more information is printed.

The parameter takes an integer value in range 0..5.

The default value is 0.

fdsInitialRating: float

Initial rating for newly created choices

Default rating for newly created choices. Both left and right branches get the same rating. Choice is initially permuted so that bigger domain change is the left branch.

The parameter takes a floating point value in range 0.0..2.0.

The default value is 0.5.

fdsReductionWeight: float

Weight of the reduction factor in rating computation

When computing the local rating of a branch, multiply reduction factor by the given weight.

The parameter takes a floating point value in range 0.0..Infinity.

The default value is 1.

fdsRatingAverageLength: int

Length of average rating computed for choices

For the computation of rating of a branch. Arithmetic average is used until the branch is taken at least FDSRatingAverageLength times. After that exponential moving average is used with parameter alpha = 1 - 1 / FDSRatingAverageLength.

The parameter takes an integer value in range 0..254.

The default value is 25.

fdsFixedAlpha: float

When non-zero, alpha factor for rating updates

When this parameter is set to a non-zero, parameter FDSRatingAverageLength is ignored. Instead, the rating of a branch is computed as an exponential moving average with the given parameter alpha.

The parameter takes a floating point value in range 0..1.

The default value is 0.

fdsRatingAverageComparison: Literal['Off', 'Global', 'Depth']

Whether to compare the local rating with the average

Possible values are:

  • Off (the default): No comparison is done.

  • Global: Compare with the global average.

  • Depth: Compare with the average on the current search depth

Arithmetic average is used for global and depth averages.

The default value is Off.

fdsReductionFactor: Literal['Normal', 'Zero', 'Random']

Reduction factor R for rating computation

Possible values are:

  • Normal (the default): Normal reduction factor.

  • Zero: Factor is not used (it is 0 all the time).

  • Random: A random number in the range [0,1] is used instead.

The default value is Normal.

fdsReuseClosing: bool

Whether always reuse closing choice

Most of the time, FDS reuses closing choice automatically. This parameter enforces it all the time.

The default value is False.

fdsUniformChoiceStep: bool

Whether all initial choices have the same step length

When set, then initial choices generated on interval variables will have the same step size.

The default value is True.

fdsLengthStepRatio: float

Choice step relative to average length

Ratio of initial choice step size to the minimum length of interval variable. When FDSUniformChoiceStep is set, this ratio is used to compute global choice step using the average of interval var length. When FDSUniformChoiceStep is not set, this ratio is used to compute the choice step for every interval var individually.

The parameter takes a floating point value in range 0.0..Infinity.

The default value is 0.699999988079071.

fdsMaxInitialChoicesPerVariable: int

Maximum number of choices generated initially per a variable

Initial domains are often very large (e.g., 0..IntervalMax). Therefore initial number of generated choices is limited: only choices near startMin are kept.

The parameter takes an integer value in range 2..2147483647.

The default value is 90.

fdsAdditionalStepRatio: float

Domain split ratio when run out of choices

When all choices are decided, and a greedy algorithm cannot find a solution, then more choices are generated by splitting domains into the specified number of pieces.

The parameter takes a floating point value in range 2.0..Infinity.

The default value is 7.

fdsPresenceStatusChoices: bool

Whether to generate choices on presence status

Choices on start time also include a choice on presence status. Therefore, dedicated choices on presence status only are not mandatory.

The default value is True.

fdsMaxInitialLengthChoices: int

Maximum number of initial choices on length of an interval variable

When non-zero, this parameter limits the number of initial choices generated on length of an interval variable. When zero (the default), no choices on length are generated.

The parameter takes an integer value in range 0..2147483647.

The default value is 0.

fdsMinLengthChoiceStep: int

Maximum step when generating initial choices for length of an interval variable

Steps between choices for length of an interval variable are never bigger than the specified value.

The parameter takes an integer value in range 1..1073741823.

The default value is 1073741823.

fdsMinIntVarChoiceStep: int

Minimum step when generating choices for integer variables.

Steps between choices for integer variables are never smaller than the specified value.

The parameter takes an integer value in range 1..1073741823.

The default value is 1073741823.

fdsEventTimeInfluence: float

Influence of event time to initial choice rating

When non-zero, the initial choice rating is influenced by the date of the choice. This way, very first choices in the search should be taken chronologically.

The parameter takes a floating point value in range 0..1.

The default value is 0.

fdsBothFailRewardFactor: float

How much to improve rating when both branches fail immediately

This parameter sets a bonus reward for a choice when both left and right branches fail immediately. Current rating of both branches is multiplied by the specified value.

The parameter takes a floating point value in range 0..1.

The default value is 0.98.

fdsEpsilon: float

How often to chose a choice randomly

Probability that a choice is taken randomly. A randomly selected choice is not added to the search tree automatically. Instead, the choice is tried, its rating is updated, but it is added to the search tree only if one of the branches fails. The mechanism is similar to strong branching.

The parameter takes a floating point value in range 0.0..0.99999.

The default value is 0.1.

fdsStrongBranchingSize: int

Number of choices to try in strong branching

Strong branching means that instead of taking a choice with the best rating, we take the specified number (FDSStrongBranchingSize) of best choices, try them in dry-run mode, measure their local rating, and then chose the one with the best local rating.

The parameter takes an integer value.

The default value is 10.

fdsStrongBranchingDepth: int

Up-to what search depth apply strong branching

Strong branching is typically used in the root node. This parameter controls the maximum search depth when strong branching is used.

The parameter takes an integer value.

The default value is 6.

fdsStrongBranchingCriterion: Literal['Both', 'Left', 'Right']

How to choose the best choice in strong branching

Possible values are:

  • Both: Choose the the choice with best combined rating.

  • Left (the default): Choose the choice with the best rating of the left branch.

  • Right: Choose the choice with the best rating of the right branch.

The default value is Left.

fdsInitialRestartLimit: int

Fail limit for the first restart

Failure-directed search is periodically restarted: explored part of the current search tree is turned into a no-good constraint, and the search starts again in the root node. This parameter specifies the size of the very first search tree (measured in number of failures).

The parameter takes an integer value in range 1..9223372036854775807.

The default value is 100.

fdsRestartStrategy: Literal['Geometric', 'Nested', 'Luby']

Restart strategy to use

This parameter specifies how the restart limit (maximum number of failures) changes from restart to restart. Possible values are:

The default value is Geometric.

fdsRestartGrowthFactor: float

Growth factor for fail limit after each restart

After each restart, the fail limit for the restart is multiplied by the specified factor. This parameter is ignored when Parameters.fdsRestartStrategy is Luby.

The parameter takes a floating point value in range 1.0..Infinity.

The default value is 1.15.

fdsMaxCounterAfterRestart: int

Truncate choice use counts after a restart to this value

The idea is that ratings learned in the previous restart are less valid in the new restart. Using this parameter, it is possible to truncate use counts on choices so that new local ratings will have bigger weights (when FDSFixedAlpha is not used).

The parameter takes an integer value.

The default value is 255.

fdsMaxCounterAfterSolution: int

Truncate choice use counts after a solution is found

Similar to Parameters.fdsMaxCounterAfterRestart, this parameter allows truncating use counts on choices when a solution is found.

The parameter takes an integer value.

The default value is 255.

fdsResetRestartsAfterSolution: bool

Reset restart size after a solution is found (ignored in Luby)

When this parameter is set (the default), then restart limit is set back to Parameters.fdsInitialRestartLimit when a solution is found.

The default value is True.

fdsUseNogoods: bool

Whether to use or not nogood constraints

By default, no-good constraint is generated after each restart. This parameter allows to turn no-good constraints off.

The default value is True.

fdsBranchOnObjective: bool

Whether to generate choices for objective expression/variable

This option controls the generation of choices on the objective. It works regardless of the objective is given by an expression or a variable.

The default value is False.

fdsBranchOrdering: Literal['FailureFirst', 'FailureLast', 'Random']

Controls which side of a choice is explored first (considering the rating).

This option can take the following values:

  • FailureFirst: Explore the failure side first.

  • FailureLast: Explore the failure side last.

  • Random: Explore either side randomly.

The default value is FailureFirst.

fdsDualStrategy: Literal['Minimum', 'Random', 'Split']

A strategy to choose objective cuts during FDSDual search.

Possible values are:

  • Minimum: Always change the cut by the minimum amount.

  • Random: At each restart, randomly choose a value in range LB..UB. The default.

  • Split: Always split the current range LB..UB in half.

The default value is Random.

fdsDualResetRatings: bool

Whether to reset ratings when a new LB is proved

When this parameter is on, and FDSDual proves a new lower bound, then all ratings are reset to default values.

The default value is False.

lnsUseWarmStartOnly: bool

Use only the user-provided warm start as the initial solution in LNS

When this parameter is on, the solver will use only the user-specified warm start solution for the initial solution phase in LNS. If no warm start is provided, the solver will search for its own initial solution as usual.

The default value is False.

simpleLBWorker: int

Which worker computes simple lower bound

Simple lower bound is a bound such that infeasibility of a better objective can be proved by propagation only (without the search). The given worker computes simple lower bound before it starts the normal search. If a worker with the given number doesn’t exist, then the lower bound is not computed.

The parameter takes an integer value in range -1..2147483647.

The default value is 0.

simpleLBMaxIterations: int

Maximum number of feasibility checks

Simple lower bound is computed by binary search for the best objective value that is not infeasible by propagation. This parameter limits the maximum number of iterations of the binary search. When the value is 0, then simple lower bound is not computed at all.

The parameter takes an integer value in range 0..2147483647.

The default value is 2147483647.

simpleLBShavingRounds: int

Number of shaving rounds

When non-zero, the solver shaves on variable domains to improve the lower bound. This parameter controls the number of shaving rounds.

The parameter takes an integer value in range 0..2147483647.

The default value is 0.

workers: list[WorkerParameters]

Per-worker parameter overrides.

Each worker can have its own parameters. If a parameter is not specified for a worker, then the global value is used.

Note that parameter Parameters.nbWorkers specifies the number of workers regardless of the length of this list.

See also

pythonStreamBufferSize: int

Size of the buffer for streaming solver output to Python.

The solver output (logs) is streamed to Python in chunks of this size (in bytes). The default value is 2 MB (2097152 bytes).

This parameter is Python-specific and does not exist in other APIs.

printLog: IO[str] | bool

Where to write solver log output.

Controls where solver log messages, warnings, and errors are written during solving.

  • None (default): Write to console (sys.stdout)

  • False: Suppress all output

  • True: Write to console (explicit)

  • File-like object: Write to the provided stream

Note that setting printLog to False only suppresses writing to the output stream. The solver still emits log, warning, and error events that can be intercepted using callback properties (Solver.on_log, Solver.on_warning, Solver.on_error). To reduce the amount of logging at the source, use Parameters.logLevel.

ANSI colors: When writing to a stream, the solver automatically detects whether the stream supports colors by checking if it is a TTY. To override automatic detection, use the Parameters.color parameter.

If the output stream becomes non-writable (e.g., a broken pipe), then the solver stops as soon as possible.

# Default - logs to console
result = model.solve()

# Silent - no output
result = model.solve({'printLog': False})

# Custom stream
with open('solver.log', 'w') as f:
    result = model.solve({'printLog': f})

See also

solver: str

Path to the solver executable or WebSocket URL.

Specifies how to connect to the solver.

The value should be a path to the optalcp executable (e.g., /usr/bin/optalcp). The API spawns the solver as a subprocess.

If not specified, the solver is searched as described in Solver.find_solver().

See also

solverArgs: list[str]

Additional command-line arguments for the solver subprocess.

These arguments are passed directly to the solver subprocess when it is spawned. This parameter is only used in subprocess mode (not when connecting to a remote solver via WebSocket).

This can be useful for debugging or passing special flags to the solver that are not exposed through the Parameters API.

import optalcp as cp

model = cp.Model()
# ... build model ...

# Pass custom arguments to the solver
result = model.solve({
    'solverArgs': ['--some-debug-flag'],
    'timeLimit': 60
})

See also

class optalcp.WorkerParameters

Bases: TypedDict

WorkerParameters specify the behavior of each worker separately. It is part of the Parameters object.

If a parameter is not listed here, then it can be set only globally (in Parameters), not per worker. For example, timeLimit or logPeriod are global parameters.

searchType: Literal['Auto', 'LNS', 'FDS', 'FDSDual', 'SetTimes', 'FDSLB']

Type of search to use

This parameter controls which search algorithm the solver uses. Different search types have different strengths:

  • Auto: Automatically determined based on the Parameters.preset (the default). With the Default preset, workers are distributed across LNS, FDS, and FDSDual. With the Large preset, all workers use LNS.

  • LNS: Large Neighborhood Search. Starts from an initial solution and iteratively improves it by relaxing and re-optimizing parts of the solution. Good for finding high-quality solutions quickly, especially on large problems. Works best when a good initial solution can be found.

  • FDS: Failure-Directed Search. A systematic search that learns from failures to guide exploration. Uses restarts with no-good learning. Often effective at proving optimality and works well with strong propagation.

  • FDSDual: Failure-Directed Search working on objective bounds. Similar to FDS but focuses on proving bounds on the objective value. Useful for optimization problems where you want to know how far from optimal your solutions are.

  • SetTimes: Depth-first set-times search (not restarted). A simple chronological search that assigns start times in order. Can be effective for tightly constrained problems but generally less robust than other methods.

Interaction with presets:

When searchType is set to Auto, the actual search type is determined by the Parameters.preset:

  • Default preset: Distributes workers across different search types. Half use LNS, 3/8 use FDS, and the rest use FDSDual. This portfolio approach provides robustness across different problem types.

  • Large preset: All workers use LNS. For very large problems, the overhead of systematic search methods like FDS becomes prohibitive, so LNS is used exclusively.

If you explicitly set searchType to a specific value (not Auto), that value is used regardless of the preset.

import optalcp as cp

model = cp.Model()
# ... build your model ...

# Let the preset decide (default behavior)
result = model.solve()

# Or explicitly use FDS for systematic search
result = model.solve(searchType="FDS")

# Or use LNS for quick solutions on large problems
result = model.solve(searchType="LNS")

See also

randomSeed: int

Random seed

The solver breaks ties randomly using a pseudorandom number generator. This parameter sets the seed of the generator.

Note that when Parameters.nbWorkers is more than 1 then there is also another source of randomness: the time it takes for a message to pass from one worker to another. Therefore with 1 worker the solver is deterministic (random behavior depends only on random seed). With more workers the solver is not deterministic.

Even with the same random seed, the solver may behave differently on different platforms. This can be due to different implementations of certain functions such as std::sort.

The parameter takes an integer value.

The default value is 1.

noOverlapPropagationLevel: int

How much to propagate noOverlap constraints

This parameter controls the amount of propagation done for noOverlap constraints. Higher levels use more sophisticated algorithms that can detect more infeasibilities and prune more values from domains, but at the cost of increased computation time.

Propagation levels:

  • Level 1: Basic timetable propagation only

  • Level 2: Adds detectable precedences algorithm

  • Level 3: Adds edge-finding reasoning

  • Level 4: Maximum propagation with all available algorithms

Automatic selection (level 0):

When set to 0 (the default), the propagation level is determined automatically based on the Parameters.preset:

  • Default preset: Uses level 4 (maximum propagation)

  • Large preset: Uses level 1 (minimum propagation for scalability)

Performance considerations:

More propagation doesn’t necessarily mean better overall performance. The trade-off depends on your problem:

  • Dense scheduling problems with many overlapping intervals often benefit from higher propagation levels because the extra pruning reduces the search space significantly.

  • Sparse problems or very large problems may perform better with lower propagation levels because the overhead of sophisticated algorithms outweighs the benefit.

  • FDS search (see Parameters.searchType) typically benefits from higher propagation levels because it relies on strong propagation to guide the search.

If you’re unsure, start with the automatic selection (level 0) and let the preset choose. You can then experiment with explicit levels if needed.

import optalcp as cp

model = cp.Model()
# ... build your model with noOverlap constraints ...

# Let the preset decide (default)
result = model.solve()

# Or use maximum propagation for dense problems
result = model.solve(noOverlapPropagationLevel=4)

# Or use minimum propagation for very large problems
result = model.solve(noOverlapPropagationLevel=1)

See also

cumulPropagationLevel: int

How much to propagate constraints on cumul functions

This parameter controls the amount of propagation done for cumulative constraints (e.g., cumul <= limit) when used with a sum of Model.pulse() pulses.

Higher levels use more sophisticated algorithms that can detect more infeasibilities and prune more values from domains, but at the cost of increased computation time.

Propagation levels:

  • Level 1: Basic timetable propagation

  • Level 2: Adds time-table edge-finding

  • Level 3: Maximum propagation with all available algorithms

Automatic selection (level 0):

When set to 0 (the default), the propagation level is determined automatically based on the Parameters.preset:

  • Default preset: Uses level 3 (maximum propagation)

  • Large preset: Uses level 1 (minimum propagation for scalability)

Performance considerations:

More propagation doesn’t necessarily mean better overall performance. The trade-off depends on your problem:

  • Resource-constrained problems with tight capacity limits often benefit from higher propagation levels because cumulative reasoning can prune many infeasible assignments.

  • Problems with loose resource constraints may not benefit much from higher levels because the extra computation doesn’t lead to significant pruning.

  • Very large problems may perform better with lower propagation levels because the overhead becomes prohibitive.

  • FDS search (see Parameters.searchType) typically benefits from higher propagation levels.

If you’re unsure, start with the automatic selection (level 0) and let the preset choose.

import optalcp as cp

model = cp.Model()
# ... build your model with cumulative constraints ...

# Let the preset decide (default)
result = model.solve()

# Or use maximum propagation for resource-constrained problems
result = model.solve(cumulPropagationLevel=3)

# Or use minimum propagation for very large problems
result = model.solve(cumulPropagationLevel=1)

See also

reservoirPropagationLevel: int

How much to propagate constraints on cumul functions

This parameter controls the amount of propagation done for cumulative constraints (e.g., cumul <= limit, cumul >= limit) when used together with steps (Model.step_at_start(), Model.step_at_end(), Model.step_at()). The bigger the value, the more algorithms are used for propagation. It means that more time is spent by the propagation, and possibly more values are removed from domains. More propagation doesn’t necessarily mean better performance. FDS search (see Parameters.searchType) usually benefits from higher propagation levels.

The parameter takes an integer value in range 1..2.

The default value is 1.

positionPropagationLevel: int

How much to propagate position expressions on noOverlap constraints

This parameter controls the amount of propagation done for position expressions on noOverlap constraints. The bigger the value, the more algorithms are used for propagation. It means that more time is spent by the propagation, and possibly more values are removed from domains. However, more propagation doesn’t necessarily mean better performance. FDS search (see Parameters.searchType) usually benefits from higher propagation levels.

The parameter takes an integer value in range 1..3.

The default value is 2.

integralPropagationLevel: int

How much to propagate integral expression

This parameter controls the amount of propagation done for Model.integral() expressions. In particular, it controls whether the propagation also affects the minimum and the maximum length of the associated interval variable:

  • 1: The length is updated only once during initial constraint propagation.

  • 2: The length is updated every time the expression is propagated.

The parameter takes an integer value in range 1..2.

The default value is 1.

searchTraceLevel: int

Level of search trace

This parameter is available only in the development edition of the solver.

When set to a value bigger than zero, the solver prints a trace of the search. The trace contains information about every choice taken by the solver. The higher the value, the more information is printed.

The parameter takes an integer value in range 0..5.

The default value is 0.

propagationTraceLevel: int

Level of propagation trace

This parameter is available only in the development edition of the solver.

When set to a value bigger than zero, the solver prints a trace of the propagation, that is a line for every domain change. The higher the value, the more information is printed.

The parameter takes an integer value in range 0..5.

The default value is 0.

fdsInitialRating: float

Initial rating for newly created choices

Default rating for newly created choices. Both left and right branches get the same rating. Choice is initially permuted so that bigger domain change is the left branch.

The parameter takes a floating point value in range 0.0..2.0.

The default value is 0.5.

fdsReductionWeight: float

Weight of the reduction factor in rating computation

When computing the local rating of a branch, multiply reduction factor by the given weight.

The parameter takes a floating point value in range 0.0..Infinity.

The default value is 1.

fdsRatingAverageLength: int

Length of average rating computed for choices

For the computation of rating of a branch. Arithmetic average is used until the branch is taken at least FDSRatingAverageLength times. After that exponential moving average is used with parameter alpha = 1 - 1 / FDSRatingAverageLength.

The parameter takes an integer value in range 0..254.

The default value is 25.

fdsFixedAlpha: float

When non-zero, alpha factor for rating updates

When this parameter is set to a non-zero, parameter FDSRatingAverageLength is ignored. Instead, the rating of a branch is computed as an exponential moving average with the given parameter alpha.

The parameter takes a floating point value in range 0..1.

The default value is 0.

fdsRatingAverageComparison: Literal['Off', 'Global', 'Depth']

Whether to compare the local rating with the average

Possible values are:

  • Off (the default): No comparison is done.

  • Global: Compare with the global average.

  • Depth: Compare with the average on the current search depth

Arithmetic average is used for global and depth averages.

The default value is Off.

fdsReductionFactor: Literal['Normal', 'Zero', 'Random']

Reduction factor R for rating computation

Possible values are:

  • Normal (the default): Normal reduction factor.

  • Zero: Factor is not used (it is 0 all the time).

  • Random: A random number in the range [0,1] is used instead.

The default value is Normal.

fdsReuseClosing: bool

Whether always reuse closing choice

Most of the time, FDS reuses closing choice automatically. This parameter enforces it all the time.

The default value is False.

fdsUniformChoiceStep: bool

Whether all initial choices have the same step length

When set, then initial choices generated on interval variables will have the same step size.

The default value is True.

fdsLengthStepRatio: float

Choice step relative to average length

Ratio of initial choice step size to the minimum length of interval variable. When FDSUniformChoiceStep is set, this ratio is used to compute global choice step using the average of interval var length. When FDSUniformChoiceStep is not set, this ratio is used to compute the choice step for every interval var individually.

The parameter takes a floating point value in range 0.0..Infinity.

The default value is 0.699999988079071.

fdsMaxInitialChoicesPerVariable: int

Maximum number of choices generated initially per a variable

Initial domains are often very large (e.g., 0..IntervalMax). Therefore initial number of generated choices is limited: only choices near startMin are kept.

The parameter takes an integer value in range 2..2147483647.

The default value is 90.

fdsAdditionalStepRatio: float

Domain split ratio when run out of choices

When all choices are decided, and a greedy algorithm cannot find a solution, then more choices are generated by splitting domains into the specified number of pieces.

The parameter takes a floating point value in range 2.0..Infinity.

The default value is 7.

fdsPresenceStatusChoices: bool

Whether to generate choices on presence status

Choices on start time also include a choice on presence status. Therefore, dedicated choices on presence status only are not mandatory.

The default value is True.

fdsMaxInitialLengthChoices: int

Maximum number of initial choices on length of an interval variable

When non-zero, this parameter limits the number of initial choices generated on length of an interval variable. When zero (the default), no choices on length are generated.

The parameter takes an integer value in range 0..2147483647.

The default value is 0.

fdsMinLengthChoiceStep: int

Maximum step when generating initial choices for length of an interval variable

Steps between choices for length of an interval variable are never bigger than the specified value.

The parameter takes an integer value in range 1..1073741823.

The default value is 1073741823.

fdsMinIntVarChoiceStep: int

Minimum step when generating choices for integer variables.

Steps between choices for integer variables are never smaller than the specified value.

The parameter takes an integer value in range 1..1073741823.

The default value is 1073741823.

fdsEventTimeInfluence: float

Influence of event time to initial choice rating

When non-zero, the initial choice rating is influenced by the date of the choice. This way, very first choices in the search should be taken chronologically.

The parameter takes a floating point value in range 0..1.

The default value is 0.

fdsBothFailRewardFactor: float

How much to improve rating when both branches fail immediately

This parameter sets a bonus reward for a choice when both left and right branches fail immediately. Current rating of both branches is multiplied by the specified value.

The parameter takes a floating point value in range 0..1.

The default value is 0.98.

fdsEpsilon: float

How often to chose a choice randomly

Probability that a choice is taken randomly. A randomly selected choice is not added to the search tree automatically. Instead, the choice is tried, its rating is updated, but it is added to the search tree only if one of the branches fails. The mechanism is similar to strong branching.

The parameter takes a floating point value in range 0.0..0.99999.

The default value is 0.1.

fdsStrongBranchingSize: int

Number of choices to try in strong branching

Strong branching means that instead of taking a choice with the best rating, we take the specified number (FDSStrongBranchingSize) of best choices, try them in dry-run mode, measure their local rating, and then chose the one with the best local rating.

The parameter takes an integer value.

The default value is 10.

fdsStrongBranchingDepth: int

Up-to what search depth apply strong branching

Strong branching is typically used in the root node. This parameter controls the maximum search depth when strong branching is used.

The parameter takes an integer value.

The default value is 6.

fdsStrongBranchingCriterion: Literal['Both', 'Left', 'Right']

How to choose the best choice in strong branching

Possible values are:

  • Both: Choose the the choice with best combined rating.

  • Left (the default): Choose the choice with the best rating of the left branch.

  • Right: Choose the choice with the best rating of the right branch.

The default value is Left.

fdsInitialRestartLimit: int

Fail limit for the first restart

Failure-directed search is periodically restarted: explored part of the current search tree is turned into a no-good constraint, and the search starts again in the root node. This parameter specifies the size of the very first search tree (measured in number of failures).

The parameter takes an integer value in range 1..9223372036854775807.

The default value is 100.

fdsRestartStrategy: Literal['Geometric', 'Nested', 'Luby']

Restart strategy to use

This parameter specifies how the restart limit (maximum number of failures) changes from restart to restart. Possible values are:

The default value is Geometric.

fdsRestartGrowthFactor: float

Growth factor for fail limit after each restart

After each restart, the fail limit for the restart is multiplied by the specified factor. This parameter is ignored when Parameters.fdsRestartStrategy is Luby.

The parameter takes a floating point value in range 1.0..Infinity.

The default value is 1.15.

fdsMaxCounterAfterRestart: int

Truncate choice use counts after a restart to this value

The idea is that ratings learned in the previous restart are less valid in the new restart. Using this parameter, it is possible to truncate use counts on choices so that new local ratings will have bigger weights (when FDSFixedAlpha is not used).

The parameter takes an integer value.

The default value is 255.

fdsMaxCounterAfterSolution: int

Truncate choice use counts after a solution is found

Similar to Parameters.fdsMaxCounterAfterRestart, this parameter allows truncating use counts on choices when a solution is found.

The parameter takes an integer value.

The default value is 255.

fdsResetRestartsAfterSolution: bool

Reset restart size after a solution is found (ignored in Luby)

When this parameter is set (the default), then restart limit is set back to Parameters.fdsInitialRestartLimit when a solution is found.

The default value is True.

fdsUseNogoods: bool

Whether to use or not nogood constraints

By default, no-good constraint is generated after each restart. This parameter allows to turn no-good constraints off.

The default value is True.

fdsBranchOnObjective: bool

Whether to generate choices for objective expression/variable

This option controls the generation of choices on the objective. It works regardless of the objective is given by an expression or a variable.

The default value is False.

fdsBranchOrdering: Literal['FailureFirst', 'FailureLast', 'Random']

Controls which side of a choice is explored first (considering the rating).

This option can take the following values:

  • FailureFirst: Explore the failure side first.

  • FailureLast: Explore the failure side last.

  • Random: Explore either side randomly.

The default value is FailureFirst.

fdsDualStrategy: Literal['Minimum', 'Random', 'Split']

A strategy to choose objective cuts during FDSDual search.

Possible values are:

  • Minimum: Always change the cut by the minimum amount.

  • Random: At each restart, randomly choose a value in range LB..UB. The default.

  • Split: Always split the current range LB..UB in half.

The default value is Random.

fdsDualResetRatings: bool

Whether to reset ratings when a new LB is proved

When this parameter is on, and FDSDual proves a new lower bound, then all ratings are reset to default values.

The default value is False.

lnsUseWarmStartOnly: bool

Use only the user-provided warm start as the initial solution in LNS

When this parameter is on, the solver will use only the user-specified warm start solution for the initial solution phase in LNS. If no warm start is provided, the solver will search for its own initial solution as usual.

The default value is False.

simpleLBWorker: int

Which worker computes simple lower bound

Simple lower bound is a bound such that infeasibility of a better objective can be proved by propagation only (without the search). The given worker computes simple lower bound before it starts the normal search. If a worker with the given number doesn’t exist, then the lower bound is not computed.

The parameter takes an integer value in range -1..2147483647.

The default value is 0.

simpleLBMaxIterations: int

Maximum number of feasibility checks

Simple lower bound is computed by binary search for the best objective value that is not infeasible by propagation. This parameter limits the maximum number of iterations of the binary search. When the value is 0, then simple lower bound is not computed at all.

The parameter takes an integer value in range 0..2147483647.

The default value is 2147483647.

simpleLBShavingRounds: int

Number of shaving rounds

When non-zero, the solver shaves on variable domains to improve the lower bound. This parameter controls the number of shaving rounds.

The parameter takes an integer value in range 0..2147483647.

The default value is 0.

Results

class optalcp.SolveResult

Bases: object

The result returned by Model.solve() or Solver.solve().

Contains comprehensive information about the solve:

Solution data:

Solve statistics:

History tracking:

Example

import optalcp as cp

model = cp.Model()
# ... build model ...

result = model.solve()

if result.solution is not None:
    print(f"Found solution with objective {result.objective}")
    print(f"Best solution found at {result.solution_time:.2f}s")
    if result.proof:
        print("Solution is optimal!")
else:
    if result.proof:
        print("Problem is infeasible")
    else:
        print("No solution found within time limit")
property nb_solutions: int

Number of solutions found during the solve.

Return type:

int

Returns:

Solution count.

property proof: bool

Whether the solve ended with a proof (optimality or infeasibility).

Return type:

bool

Returns:

True if the solve completed with a proof.

Details

When True, the solver has either:

When False, the solve was interrupted (e.g., by time limit) before a proof could be established.

property duration: float

Total duration of the solve in seconds.

Return type:

float

Returns:

Seconds elapsed.

property nb_branches: int

Total number of branches explored during the solve.

Return type:

int

Returns:

Branch count.

property nb_fails: int

Total number of failures encountered during the solve.

Return type:

int

Returns:

Failure count.

property objective: int | None

Best objective value found (for optimization problems).

Return type:

int | None

Returns:

The objective value, or None if not applicable.

Details

The value is None when:

  • No objective was specified in the model (no Model.minimize() or Model.maximize() call).

  • No solution was found.

  • The objective expression has an absent value in the best solution.

property objective_bound: int | None

Proved bound on the objective value.

Return type:

int | None

Returns:

The objective bound, or None if no bound was proved.

Details

For minimization problems, this is a lower bound: the solver proved that no solution exists with an objective value less than this bound.

For maximization problems, this is an upper bound: the solver proved that no solution exists with an objective value greater than this bound.

The value is None when no bound was proved or for satisfaction problems.

property solution: Solution | None

The best solution found during the solve.

Return type:

Solution | None

Returns:

The best solution, or None if no solution was found.

Details

For optimization problems, this is the solution with the best objective value. For satisfaction problems, this is the last solution found.

Returns None when no solution was found (the problem may be infeasible or the solve was interrupted before finding any solution).

property nb_lns_steps: int

Total number of Large Neighborhood Search steps.

Return type:

int

Returns:

LNS step count.

property nb_restarts: int

Total number of restarts performed during the solve.

Return type:

int

Returns:

Restart count.

property memory_used: int

Memory used by the solver in bytes.

Return type:

int

Returns:

Bytes used.

property nb_int_vars: int

Number of integer variables in the model.

Return type:

int

Returns:

Integer variable count.

property nb_interval_vars: int

Number of interval variables in the model.

Return type:

int

Returns:

Interval variable count.

property nb_constraints: int

Number of constraints in the model.

Return type:

int

Returns:

Constraint count.

property solver: str

Solver name and version string.

Return type:

str

Returns:

The solver identification string.

Details

Contains the solver name followed by its version number.

property actual_workers: int

Number of worker threads actually used during solving.

Return type:

int

Returns:

Worker count.

Details

This is the actual number of workers used by the solver, which may differ from the requested Parameters.nbWorkers if that parameter was not specified (auto-detect) or if the system has fewer cores than requested.

property cpu: str

CPU name detected by the solver.

Return type:

str

Returns:

CPU model name.

Details

Contains the CPU model name as detected by the operating system.

property objective_sense: str | None

Objective direction.

Return type:

str | None

Returns:

‘minimize’, ‘maximize’, or None for satisfaction problems.

Details

Indicates whether the model was a minimization problem, maximization problem, or a satisfaction problem (no objective).

property objective_history: Sequence[ObjectiveEntry]

History of objective value improvements during the solve.

Return type:

Sequence[ObjectiveEntry]

Returns:

Sequence of objective entries, one per solution found.

Details

Returns a sequence of ObjectiveEntry objects, one for each solution found during the solve.

Each entry contains:

The entries are ordered chronologically by solve time.

property objective_bound_history: Sequence[ObjectiveBoundEntry]

History of objective bound improvements during the solve.

Return type:

Sequence[ObjectiveBoundEntry]

Returns:

Sequence of bound entries, one per bound improvement.

Details

Returns a sequence of ObjectiveBoundEntry objects, one for each bound improvement proved during the solve.

Each entry contains:

For minimization problems, these are lower bounds. For maximization problems, these are upper bounds. The entries are ordered chronologically by solve time.

property solution_time: float | None

Time when the best solution was found.

Return type:

float | None

Returns:

The time in seconds, or None if no solution was found.

Details

The time is measured from the start of the solve, in seconds.

Returns None when no solution was found.

property bound_time: float | None

Time of the last objective bound improvement.

Return type:

float | None

Returns:

The time in seconds, or None if no bound was proved.

Details

The time is measured from the start of the solve, in seconds.

Returns None when no bound was proved during the solve.

property solution_valid: bool | None

Whether the best solution was verified.

Return type:

bool | None

Returns:

True if verified, None if verification was not performed.

Details

When parameter Parameters.verifySolutions is set to True (the default), the solver verifies all solutions found. The verification checks that all constraints in the model are satisfied and that the objective value is computed correctly.

Possible values:

  • None - verification was not performed (parameter was not set)

  • True - the solution was verified and correct

The value can never be False because, in that case, the solver would stop with an error.

class optalcp.Solution

Bases: object

Solution of a Model. When a model is solved, the solution is stored in this object. The solution contains values of all variables in the model (including optional variables) and the value of the objective (if the model specified one).

Preview version of OptalCP

Note that in the preview version of OptalCP, the values of variables in the solution are masked and replaced by value absent (None).

get_objective() int | None

Returns the objective value of the solution.

Return type:

int | None

Returns:

The objective value

Details

Returns None if no objective was specified or if the objective expression is absent (see optional IntExpr).

The correct value is reported even in the preview version of OptalCP.

is_present(variable: IntVar | BoolVar | IntervalVar) bool

Returns True if the given variable is present in the solution.

Parameters:

variable (IntVar | BoolVar | IntervalVar) – The variable to check

Return type:

bool

Returns:

True if the variable is present

Details

In the preview version of OptalCP, this method always returns False because real values of variables are masked and replaced by value absent.

is_absent(variable: IntVar | BoolVar | IntervalVar) bool

Returns True if the given variable is absent in the solution.

Parameters:

variable (IntVar | BoolVar | IntervalVar) – The variable to check

Return type:

bool

Returns:

True if the variable is absent

Details

In the preview version of OptalCP, this method always returns True because real values of variables are masked and replaced by value absent.

get_value(variable: IntVar) int | None
get_value(variable: BoolVar) bool | None
get_value(variable: IntervalVar) tuple[int, int] | None

Overload 1: (variable: IntVar) -> int | None

Gets an integer variable’s value from the solution.

Parameters:

variable (IntVar) – The integer variable to get the value of

Return type:

int | None

Returns:

The value, or None if the variable is absent

Overload 2: (variable: BoolVar) -> bool | None

Gets a boolean variable’s value from the solution.

Parameters:

variable (BoolVar) – The boolean variable to get the value of

Return type:

bool | None

Returns:

The value, or None if the variable is absent

Overload 3: (variable: IntervalVar) -> tuple[int, int] | None

Gets an interval variable’s start and end from the solution.

Parameters:

variable (IntervalVar) – The interval variable to get the value of

Return type:

tuple[int, int] | None

Returns:

A tuple (start, end), or None if the interval is absent

get_start(variable: IntervalVar) int | None

Gets an interval variable’s start time from the solution.

Parameters:

variable (IntervalVar) – The interval variable to query

Return type:

int | None

Returns:

The start value, or None if absent

Details

If the variable is absent in the solution, it returns None.

In the preview version of OptalCP, this function always returns None because real values of variables are masked and replaced by value absent.

get_end(variable: IntervalVar) int | None

Gets an interval variable’s end time from the solution.

Parameters:

variable (IntervalVar) – The interval variable to query

Return type:

int | None

Returns:

The end value, or None if absent

Details

If the variable is absent in the solution, it returns None.

In the preview version of OptalCP, this function always returns None because real values of variables are masked and replaced by value absent.

get_length(variable: IntervalVar) int | None

Gets an interval variable’s length from the solution.

Parameters:

variable (IntervalVar) – The interval variable to query

Return type:

int | None

Returns:

The length value, or None if absent

Details

If the variable is absent in the solution, it returns None.

In the preview version of OptalCP, this function always returns None because real values of variables are masked and replaced by value absent.

set_objective(value: int | None) None

Sets objective value of the solution.

Parameters:

value (int | None) – The objective value to set

Details

This function can be used for construction of an external solution that can be passed to the solver (see Model.solve(), Solver and Solver.send_solution()).

set_value(variable: IntVar, value: int) None
set_value(variable: BoolVar, value: bool) None
set_value(variable: IntervalVar, start: int, end: int) None

Overload 1: (variable: IntVar, value: int) -> None

Sets the value of the given integer variable in the solution.

Parameters:
  • variable (IntVar) – The integer variable

  • value (int) – The value to set

Details

The variable will be present in the solution with the given value.

See also

Overload 2: (variable: BoolVar, value: bool) -> None

Sets the value of the given boolean variable in the solution.

param variable:

The boolean variable

type variable:

BoolVar

param value:

The value to set (True or False)

type value:

bool

Details

The variable will be present in the solution with the given value.

See also

Overload 3: (variable: IntervalVar, start: int, end: int) -> None

Sets the start and end of the given interval variable in the solution.

param variable:

The interval variable

type variable:

IntervalVar

param start:

The start time

type start:

int

param end:

The end time

type end:

int

Details

The interval variable will be present in the solution with the given start and end.

See also

set_absent(variable: IntVar | BoolVar | IntervalVar) None

Sets the given variable to be absent in the solution.

Parameters:

variable (IntVar | BoolVar | IntervalVar) – The variable to set as absent

Details

This function can be used for construction of an external solution that can be passed to the solver (see Model.solve() and Solver.send_solution()).

final class optalcp.SolveSummary

Bases: object

Summary statistics from the solver at completion.

Contains statistics about the solve including the number of solutions found, the total duration, search statistics (branches, fails, restarts), and information about the model and environment.

This class is passed to the on_summary callback. For a richer interface with additional tracking data (solution history, objective bounds history), see SolveResult.

property nb_solutions: int

Number of solutions found during the solve.

Return type:

int

Returns:

Solution count.

property proof: bool

Whether the solve ended with a proof (optimality or infeasibility).

Return type:

bool

Returns:

True if the solve completed with a proof.

Details

When True, the solver has either:

When False, the solve was interrupted (e.g., by time limit) before a proof could be established.

property duration: float

Total duration of the solve in seconds.

Return type:

float

Returns:

Seconds elapsed.

property nb_branches: int

Total number of branches explored during the solve.

Return type:

int

Returns:

Branch count.

property nb_fails: int

Total number of failures encountered during the solve.

Return type:

int

Returns:

Failure count.

property nb_lns_steps: int

Total number of Large Neighborhood Search steps.

Return type:

int

Returns:

LNS step count.

property nb_restarts: int

Total number of restarts performed during the solve.

Return type:

int

Returns:

Restart count.

property memory_used: int

Memory used by the solver in bytes.

Return type:

int

Returns:

Bytes used.

property objective: int | None

Best objective value found (for optimization problems).

Return type:

int | None

Returns:

The objective value, or None if not applicable.

Details

The value is None when:

  • No objective was specified in the model (no Model.minimize() or Model.maximize() call).

  • No solution was found.

  • The objective expression has an absent value in the best solution.

property objective_bound: int | None

Proved bound on the objective value.

Return type:

int | None

Returns:

The objective bound, or None if no bound was proved.

Details

For minimization problems, this is a lower bound: the solver proved that no solution exists with an objective value less than this bound.

For maximization problems, this is an upper bound: the solver proved that no solution exists with an objective value greater than this bound.

The value is None when no bound was proved or for satisfaction problems.

property objective_sense: str | None

Objective direction.

Return type:

str | None

Returns:

‘minimize’, ‘maximize’, or None for satisfaction problems.

Details

Indicates whether the model was a minimization problem, maximization problem, or a satisfaction problem (no objective).

property nb_int_vars: int

Number of integer variables in the model.

Return type:

int

Returns:

Integer variable count.

property nb_interval_vars: int

Number of interval variables in the model.

Return type:

int

Returns:

Interval variable count.

property nb_constraints: int

Number of constraints in the model.

Return type:

int

Returns:

Constraint count.

property solver: str

Solver name and version string.

Return type:

str

Returns:

The solver identification string.

Details

Contains the solver name followed by its version number.

property actual_workers: int

Number of worker threads actually used during solving.

Return type:

int

Returns:

Worker count.

Details

This is the actual number of workers used by the solver, which may differ from the requested Parameters.nbWorkers if that parameter was not specified (auto-detect) or if the system has fewer cores than requested.

property cpu: str

CPU name detected by the solver.

Return type:

str

Returns:

CPU model name.

Details

Contains the CPU model name as detected by the operating system.

final class optalcp.SolutionEvent

Bases: object

An event emitted when a solution is found.

This event is passed to the Solver.on_solution callback and contains the solution, solving time so far, and the result of solution verification.

import optalcp as cp

model = cp.Model()
x = model.interval_var(length=10, name="x")
model.minimize(x.end())

async def handle_solution(event: cp.SolutionEvent):
    print(f"Solution found at {event.solve_time:.2f}s")
    print(f"Objective: {event.solution.get_objective()}")
    if event.valid is not None:
        print(f"Verified: {event.valid}")

solver = cp.Solver(on_solution=handle_solution)
result = await solver.solve(model)

See also

solve_time: float

The duration of the solve at the time the solution was found, in seconds.

solution: Solution

The solution containing values for all variables and the objective value.

The solution contains values of all variables in the model (including optional variables) and the value of the objective (if the model specified one).

See also

  • Solution for accessing variable values.

valid: bool | None

Result of the verification of the solution.

When parameter Parameters.verifySolutions is set to True (the default), the solver verifies all solutions found. The verification checks that all constraints in the model are satisfied and that the objective value is computed correctly.

The verification is done using a separate code (not used during the search). The point is to independently verify the correctness of the solution.

Possible values are:

  • None - the solution was not verified (because the parameter Parameters.verifySolutions was not set).

  • True - the solution was verified and correct.

The value can never be False because, in that case, the solver ends with an error.

final class optalcp.ObjectiveEntry

Bases: object

Single entry in the objective value history.

Tracks when each improving solution was found during the solve, along with its objective value and validation status.

There is one entry for each solution found. The entries are ordered by solve time.

See also

solve_time: float

Duration of the solve when this solution was found, in seconds.

Return type:

float

Returns:

Seconds elapsed.

objective: int | None

The objective value of this solution.

Return type:

int | None

Returns:

The objective value, or None if not applicable.

Details

The value is None when:

valid: bool | None

Whether this solution was verified (if verification is enabled).

Return type:

bool | None

Returns:

True if verified, None if not verified.

Details

When parameter Parameters.verifySolutions is set to True (the default), the solver verifies all solutions found. The verification checks that all constraints in the model are satisfied and that the objective value is computed correctly.

The verification is done using separate code (not used during the search). The point is to independently verify the correctness of the solution.

Possible values are:

  • None - the solution was not verified (because the parameter

    Parameters.verifySolutions was not set).

  • True - the solution was verified and correct.

The value can never be False because, in this case, the solver would stop with an error.

final class optalcp.ObjectiveBoundEntry

Bases: object

Single entry in the objective bound history.

Tracks when a new (better) bound on the objective was proved, along with the solve time and bound value. For minimization problems, this is the lower bound; for maximization, the upper bound.

See also

solve_time: float

Duration of the solve at the time the bound was found, in seconds.

Return type:

float

Returns:

Seconds elapsed.

value: int

The new bound value.

Return type:

int

Returns:

Bound value.

Details

For minimization problems, this is a lower bound on the objective. For maximization problems, this is an upper bound on the objective.

Constants

optalcp.IntVarMax = 1073741823

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4

optalcp.IntVarMin = -1073741823

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4

optalcp.IntervalMax = 715827882

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4

optalcp.IntervalMin = -715827882

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4

optalcp.LengthMax = 1431655764

int([x]) -> integer int(x, base=10) -> integer

Convert a number or string to an integer, or return 0 if no arguments are given. If x is a number, return x.__int__(). For floating-point numbers, this truncates towards zero.

If x is not a number or if base is given, then x must be a string, bytes, or bytearray instance representing an integer literal in the given base. The literal can be preceded by ‘+’ or ‘-’ and be surrounded by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. Base 0 means to interpret the base from the string as an integer literal. >>> int(‘0b100’, base=0) 4