API Reference
Model
- class optalcp.Model(*, name: str | None = None)
Bases:
objectModel 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()orModel.maximize()) and constraints (e.g.Model.no_overlap()). Note that a boolean expression becomes a constraint only by passing it toModel.enforce(); otherwise, it is not enforced.To solve a model, pass it to function
Model.solve()or toSolverclass.Available modeling elements
Variables
Interval variables can be created by function
Model.interval_var(), integer variables by functionModel.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 -, *, //.
Model.abs(): absolute value.Model.min2(): minimum of two integer expressions.Model.min(): minimum of an array of integer expressions.Model.max2(): maximum of two integer expressions.Model.max(): maximum of an array of integer expressions.Model.sum(): sum of an array of integer expressions.
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
Model.not_(): negation.Model.and_(): conjunction.Model.or_(): disjunction.Model.implies(): implication.
Functions returning
BoolExprModel.presence(): whether the argument is present or absent.Model.in_range(): whether an integer expression is within the given range
Basic constraints on interval variables
Model.alternative(): an alternative between multiple interval variables.Model.span(): span (cover) of interval variables.Model.end_before_end(),Model.end_before_start(),Model.start_before_end(),Model.start_before_start(),Model.end_at_start(),Model.start_at_end(): precedence constraints.
Disjunction (noOverlap)
Model.sequence_var(): sequence variable over a set of interval variables.Model.no_overlap(): constraints a set of interval variables to not overlap (possibly with transition times).Model.position(): returns the position of an interval variable in a sequence.
Basic cumulative expressions
Model.pulse(): changes value during the interval variable.Model.step_at_start(): changes value at the start of the interval variable.Model.step_at_end(): changes value at the end of the interval variable.Model.step_at(): changes value at a given time.
Combining cumulative expressions
Use standard operators on cumulative expressions: +, -, unary -, and
Model.sum().Constraints on cumulative expressions
Use comparison operators on cumulative expressions: <=, >=.
Objective
Model.minimize(): minimize an integer expression.Model.maximize(): maximize an integer expression.
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).")
See also
- 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:
- 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"
See also
- get_int_vars() 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"
See also
- get_bool_vars() 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"
See also
- 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:
- 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:
- Return type:
- 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:
- Return type:
- 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
Model.interval_var()for the primary variable type for scheduling problems.Model.int_var()for numeric decisions.
- 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:
- 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
SequenceVar.no_overlap()for an example of sequenceVar usage with transition times.
- 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:
- 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:
x.end() + transitions[i][j] is less than or equal to y.start().
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
SequenceVarcan be passed. SeeModel.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
SequenceVar.no_overlap()is the equivalent method onSequenceVar.
- 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:
- Returns:
The created step function
Details
Integer step function is a piecewise constant function defined on integer values in range
IntVarMintoIntVarMax. 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:
Function
Model.eval()evaluates the function at the given point (given asIntExpr).Function
Model.integral()computes a sum (integral) of the function over anIntervalVar.Constraints
Model.forbid_start()andModel.forbid_end()forbid the start/end of anIntervalVarto be in a zero-value interval of the function.Constraint
Model.forbid_extent()forbids the extent of anIntervalVarto be in a zero-value interval of the function.
- 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:
BoolExpr objects: Boolean expressions created from comparisons (e.g., x <= 5, a == b) or logical operations (e.g., a & b, ~c).
bool values: Python boolean constants True or False.
Constraint objects: Accepted but does nothing (constraints auto-register). Use with cumulative constraints for clarity: model.enforce(cumul <= capacity).
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
BoolExpr.enforce()for the fluent-style alternative.Model.no_overlap()for creating no-overlap constraints.Model.minimize()for creating minimization objectives.Model.maximize()for creating maximization objectives.
- minimize(expr: IntExpr | int) Objective
Creates a minimization objective for the provided expression.
- Parameters:
- Return type:
- Returns:
An Objective that minimizes the expression.
Details
Creates an
Objectiveto 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
IntExpr.minimize()for fluent-style minimization.
- maximize(expr: IntExpr | int) Objective
Creates a maximization objective for the provided expression.
- Parameters:
- Return type:
- Returns:
An Objective that maximizes the expression.
Details
Creates an
Objectiveto 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
IntExpr.maximize()for fluent-style maximization.
- sum(args: Iterable[IntExpr | int]) IntExpr
- sum(args: Iterable[CumulExpr]) CumulExpr
Overload 1:
(args: Iterable[IntExpr | int]) -> IntExprCreates an integer expression for the sum of the arguments.
- Parameters:
args (Iterable[IntExpr | int]) – Array of integer expressions to sum.
- Return type:
- 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]) -> CumulExprSum 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:
- 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()andIntExpr.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:
- 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()orModel.maximize()), the solver searches for optimal or near-optimal solutions within the given time limit.The returned
SolveResultcontains: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
Parametersfor 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
Solverclass 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
Solverfor async solving with event callbacks.Parametersfor available solver parameters.SolveResultfor the result structure.Solutionfor working with solutions.
- 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:
- 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
Model.from_json()to import from JSON.Model.to_text()to export as text format.Model.to_js()to export as JavaScript code.
- 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:
- 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
Model.to_js()to export as JavaScript code.Model.to_json()to export as JSON (can be imported back).
- 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:
- 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
Model.to_text()to export as text format.Model.to_json()to export as JSON (can be imported back).
- 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
Model.to_json()to export to JSON.
- not_(arg: BoolExpr | bool) BoolExpr
Negation of the boolean expression arg.
- Parameters:
- Return type:
- 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:
- 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:
- 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:
- 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:
- Return type:
- 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:
- Return type:
- 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 ≤ arg ≤ ub.
- Parameters:
- Return type:
- 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:
- Return type:
- 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:
- Return type:
- 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(). SeeModel.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:
- Return type:
- 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(). SeeModel.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:
- 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:
- 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: lhs ≤ rhs.
- Parameters:
- Return type:
- 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 lhs ≤ rhs 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
Model.lex_lt(),Model.lex_ge(),Model.lex_gt()for other lexicographic comparisons.
- lex_lt(lhs: Iterable[IntExpr | int], rhs: Iterable[IntExpr | int]) Constraint
Lexicographic strictly less than constraint: lhs < rhs.
- Parameters:
- Return type:
- 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
Model.lex_le(),Model.lex_ge(),Model.lex_gt()for other lexicographic comparisons.
- lex_ge(lhs: Iterable[IntExpr | int], rhs: Iterable[IntExpr | int]) Constraint
Lexicographic greater than or equal constraint: lhs ≥ rhs.
- Parameters:
- Return type:
- 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 lhs ≥ rhs 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
Model.lex_le(),Model.lex_lt(),Model.lex_gt()for other lexicographic comparisons.
- lex_gt(lhs: Iterable[IntExpr | int], rhs: Iterable[IntExpr | int]) Constraint
Lexicographic strictly greater than constraint: lhs > rhs.
- Parameters:
- Return type:
- 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
Model.lex_le(),Model.lex_lt(),Model.lex_ge()for other lexicographic comparisons.
- start(interval: IntervalVar) IntExpr
Creates an integer expression for the start time of an interval variable.
- Parameters:
interval (IntervalVar) – The interval variable.
- Return type:
- 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
IntervalVar.start()is equivalent function onIntervalVar.
- end(interval: IntervalVar) IntExpr
Creates an integer expression for the end time of an interval variable.
- Parameters:
interval (IntervalVar) – The interval variable.
- Return type:
- 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
IntervalVar.end()is equivalent function onIntervalVar.
- 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:
- 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
IntervalVar.length()is equivalent function onIntervalVar.
- 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:
- 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.
See also
IntervalVar.end_before_end()is equivalent function onIntervalVar.
- 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:
- 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.
See also
IntervalVar.end_before_start()is equivalent function onIntervalVar.
- 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:
- 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.
See also
IntervalVar.start_before_end()is equivalent function onIntervalVar.
- 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:
- 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.
See also
IntervalVar.start_before_start()is equivalent function onIntervalVar.
- 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:
- 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.
See also
IntervalVar.end_at_end()is equivalent function onIntervalVar.
- 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:
- 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.
See also
IntervalVar.end_at_start()is equivalent function onIntervalVar.
- 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:
- 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.
See also
IntervalVar.start_at_end()is equivalent function onIntervalVar.
- 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:
- 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.
See also
IntervalVar.start_at_start()is equivalent function onIntervalVar.
- 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:
- 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:
- 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
IntervalVar.span()is equivalent function onIntervalVar.
- position(interval: IntervalVar, sequence: SequenceVar) IntExpr
Creates an expression equal to the position of the interval on the sequence.
- Parameters:
interval (IntervalVar) – The interval variable.
sequence (SequenceVar) – The sequence variable.
- Return type:
- 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
IntervalVar.position()is equivalent function onIntervalVar.Model.no_overlap()for constraints on overlapping intervals.Model.sequence_var()for creating sequence variables.
- pulse(interval: IntervalVar, height: IntExpr | int) CumulExpr
Creates cumulative function (expression) _pulse_ for the given interval variable and height.
- Parameters:
interval (IntervalVar) – The interval variable.
- Return type:
- 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()andModel.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
IntervalVar.pulse()is equivalent function onIntervalVar.Model.step_at_start(),Model.step_at_end(),Model.step_at()for other basic cumulative functions.CumulExprfor constraining cumulative functions using comparison operators (<=, >=).
- 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:
interval (IntervalVar) – The interval variable.
- Return type:
- 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
IntervalVar.step_at_start()is equivalent function onIntervalVar.Model.step_at_end(),Model.step_at(),Model.pulse()for other basic cumulative functions.CumulExprfor constraining cumulative functions using comparison operators (<=, >=).
- 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:
interval (IntervalVar) – The interval variable.
- Return type:
- 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
IntervalVar.step_at_end()is equivalent function onIntervalVar.Model.step_at_start(),Model.step_at(),Model.pulse()for other basic cumulative functions.CumulExprfor constraining cumulative functions using comparison operators (<=, >=).
- step_at(x: int, height: IntExpr | int) CumulExpr
Creates a cumulative function that changes value at a given point.
- Parameters:
- Return type:
- Returns:
The resulting cumulative expression
Details
This function is similar to
Model.step_at_start()andModel.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 byModel.int_var()).Formal definition
step_at creates a cumulative function which has the value:
0 before x,
height after x.
See also
Model.step_at_start(),Model.step_at_end()for an example with step_at.CumulExprfor constraining cumulative functions using comparison operators (<=, >=).
- integral(func: IntStepFunction, interval: IntervalVar) IntExpr
Computes sum of values of the step function func over the interval interval.
- Parameters:
func (IntStepFunction) – The step function.
interval (IntervalVar) – The interval variable.
- Return type:
- 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
IntStepFunction.integral()for the equivalent function onIntStepFunction.
- eval(func: IntStepFunction, arg: IntExpr | int) IntExpr
Evaluates a step function at a given point.
- Parameters:
func (IntStepFunction) – The step function.
arg (IntExpr | int) – The point at which to evaluate the step function.
- Return type:
- 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()andModel.forbid_end()work that way.See also
IntStepFunction.eval()for the equivalent function onIntStepFunction.Model.forbid_start(),Model.forbid_end()are convenience functions built on top of eval.
- forbid_extent(interval: IntervalVar, func: IntStepFunction) Constraint
Forbid the interval variable to overlap with segments of the function where the value is zero.
- Parameters:
interval (IntervalVar) – The interval variable.
func (IntStepFunction) – The step function.
- Return type:
- 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
IntervalVar.forbid_extent()for the equivalent function onIntervalVar.Model.forbid_start(),Model.forbid_end()for similar functions that constrain the start/end of an interval variable.Model.eval()for evaluation of a step function.
- 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:
interval (IntervalVar) – The interval variable.
func (IntStepFunction) – The step function.
- Return type:
- 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
IntervalVar.forbid_start()for the equivalent function onIntervalVar.Model.forbid_end()for similar function that constrains end an interval variable.Model.eval()for evaluation of a step function.
- 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:
interval (IntervalVar) – The interval variable.
func (IntStepFunction) – The step function.
- Return type:
- 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
IntervalVar.forbid_end()for the equivalent function onIntervalVar.Model.forbid_start()for similar function that constrains start an interval variable.Model.eval()for evaluation of a step function.
Variables
- class optalcp.IntVar
Bases:
IntExprInteger 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
IntervalVarvariables. 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; seeModel.pulse(),Model.step_at_start(),Model.step_at_end(), andModel.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()andIntExpr.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
IntVarMintoIntVarMax.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
See also
- 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
IntVarMintoIntVarMax.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
See also
- 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:
BoolExprBoolean 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 fromIntExpr. 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()orModel.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()andIntExpr.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.BoolExprfor boolean expressions and their operations.IntVarfor integer decision variables.IntervalVarfor 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)
See also
- 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)
See also
- 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
See also
- class optalcp.IntervalVar
Bases:
ModelElementInterval 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()andIntervalVar.end(). or usingModel.start()andModel.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 usingIntervalVar.length()orModel.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()andModel.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:
- 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
Model.presence()is the equivalent function onModel.
- start() IntExpr
Creates an integer expression for the start time of the interval variable.
- Return type:
- 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
Model.start()is equivalent function onModel.
- end() IntExpr
Creates an integer expression for the end time of the interval variable.
- Return type:
- 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
Model.end()is equivalent function onModel.
- length() IntExpr
Creates an integer expression for the duration (end - start) of the interval variable.
- Return type:
- 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
Model.length()is equivalent function onModel.
- 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:
- 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
Model.end_before_end()is equivalent function onModel.
- 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:
- 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.
See also
Model.end_before_start()is equivalent function onModel.
- 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:
- 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.
See also
Model.start_before_end()is equivalent function onModel.
- 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:
- 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.
See also
Model.start_before_start()is equivalent function onModel.
- 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:
- 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
Model.end_at_end()is equivalent function onModel.
- 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:
- 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
Model.end_at_start()is equivalent function onModel.
- 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:
- 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
Model.start_at_end()is equivalent function onModel.
- 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:
- 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
Model.start_at_start()is equivalent function onModel.
- 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:
- 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:
- 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:
- 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:
- Return type:
- 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
Model.pulse()for detailed documentation and examples.
- 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:
- Return type:
- 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
Model.step_at_start()for detailed documentation.IntervalVar.step_at_end()for the opposite function.
- 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:
- Return type:
- 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
Model.step_at_end()for detailed documentation.IntervalVar.step_at_start()for the opposite function.
- 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:
- 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
Model.forbid_extent()for the equivalent function onModel.Model.forbid_start(),Model.forbid_end()for similar functions that constrain the start/end of an interval variable.Model.eval()for evaluation of a step function.
- 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:
- 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
Model.forbid_start()for the equivalent function onModel.Model.forbid_end()for similar function that constrains end an interval variable.Model.eval()for evaluation of a step function.
- 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:
- 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
Model.forbid_end()for the equivalent function onModel.Model.forbid_start()for similar function that constrains start an interval variable.Model.eval()for evaluation of a step function.
- 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
IntervalMintoIntervalMax.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
IntervalMintoIntervalMax.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
IntervalMintoIntervalMax.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
IntervalMintoIntervalMax.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:
ModelElementModels 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 theSequenceVar.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()orIntervalVar.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
Model.sequence_var()to create sequence variables.SequenceVar.no_overlap()for the no-overlap constraint with transitions.Model.position()to get an interval’s position in the sequence.
- 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:
- 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:
x.end() + transitions[x.type][y.type] is less or equal to y.start().
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 toModel.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:
objectThe 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 asIntervalVarandIntExpr.Any modeling object can be assigned a name using the
ModelElement.nameproperty.- 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:
ModelElementA 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
IntVarMintoIntVarMax.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:
- 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
Model.minimize()for Model-centric minimization.IntExpr.maximize()for maximization.
- maximize() Objective
Creates a maximization objective for this expression.
- Return type:
- 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
Model.maximize()for Model-centric maximization.IntExpr.minimize()for minimization.
- presence() BoolExpr
Returns an expression which is True if the expression is present and False when it is absent.
- Return type:
- 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:
- 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:
- Return type:
- 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 lb ≤ this ≤ ub.
- Parameters:
- Return type:
- 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:
- 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:
- Return type:
- 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(). SeeModel.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:
- Return type:
- 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(). SeeModel.max()for n-ary maximum.
- class optalcp.BoolExpr
Bases:
IntExprA 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_()orBoolExpr.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:
Both a and b are present, and a ends before b starts.
Only a is present, and b is absent.
Only b is present, and a is absent.
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
Model.enforce()for the Model-centric style of adding constraints.BoolExprfor more about boolean expressions.
- not_() BoolExpr
Returns negation of the expression.
- Return type:
- 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:
- Return type:
- 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:
- Return type:
- 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_().
- class optalcp.CumulExpr
Bases:
ModelElementCumulative 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()orIntervalVar.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(). andModel.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:
ModelElementRepresents 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 usingModel.enforce()orBoolExpr.enforce().Unlike
BoolExpr, constraints cannot be combined using logical operators likeModel.and_()/Model.or_()or &/|.Common ways to create constraints:
Scheduling constraints:
Model.no_overlap(),Model.alternative(),Model.span()Precedence constraints:
IntervalVar.end_before_start(),IntervalVar.start_at_end()Cumulative constraints: cumul <= capacity, cumul >= min_level
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.BoolExprfor boolean expressions that can also be enforced as constraints.
- class optalcp.Objective
Bases:
ModelElementRepresents 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()orModel.maximize(), or by using the fluent methodsIntExpr.minimize()orIntExpr.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
Model.minimize()for creating minimization objectives.Model.maximize()for creating maximization objectives.IntExpr.minimize()for fluent-style minimization.IntExpr.maximize()for fluent-style maximization.
Functions
- class optalcp.IntStepFunction
Bases:
ModelElementInteger step function.
Integer step function is a piecewise constant function defined on integer values in range
IntVarMintoIntVarMax. The function can be created byModel.step_function().Step functions can be used in the following ways:
Function
Model.eval()evaluates the function at the given point (given asIntExpr).Function
Model.integral()computes a sum (integral) of the function over anIntervalVar.Constraints
Model.forbid_start()andModel.forbid_end()forbid the start/end of anIntervalVarto be in a zero-value interval of the function.Constraint
Model.forbid_extent()forbids the extent of anIntervalVarto be in a zero-value interval of the function.
- integral(interval: IntervalVar) IntExpr
Computes sum of values of the step function over the interval interval.
- Parameters:
interval (IntervalVar) – The interval variable.
- Return type:
- 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
Model.integral()for the equivalent function onModel.
- 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:
- 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()andModel.forbid_end()work that way.See also
Model.eval()for the equivalent function onModel.Model.forbid_start(),Model.forbid_end()are convenience functions built on top of eval.
Solving
- class optalcp.Solver
Bases:
objectProvides 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, useModel.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:
Solver.on_error: Called with a str message when an error occurs.Solver.on_warning: Called with a str for every issued warning.Solver.on_log: Called with a str for every log message.Solver.on_solution: Called with aSolutionEventwhen a solution is found.Solver.on_objective_bound: Called with an ObjectiveBoundEntry when a new bound is proved.Solver.on_summary: Called with aSolveSummaryat the end of the solve.
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.printLogparameter.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.logLevelandParameters.logPeriod.See also
Parameters.printLogfor redirecting output to a stream.Solver.on_warningfor warning messages.
- 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
Parameters.printLogfor redirecting output to a stream.Solver.on_logfor log messages.Solver.on_errorfor error messages.
- 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
Solver.on_warningfor warning messages.Solver.on_logfor log messages.
- 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.
- event (
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
SolutionEventfor the event structure.Solutionfor accessing variable values.Solver.on_objective_boundfor objective bound updates.
- 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).
- event (
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
ObjectiveBoundEntryfor the event structure.Solver.on_solutionfor solution events with objective values.
- 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).
- summary (
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
SolveSummaryfor the complete list of statistics.
- 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:
- 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
Parameters.solverto specify the solver.
- 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:
- 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.lnsUseWarmStartOnlycontrols 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.logLevelis 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:
- 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
Model.to_text()for synchronous usage.Solver.to_js()for JavaScript export.
- 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:
- 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
Model.to_js()for synchronous usage.Solver.to_text()for text format export.
- class optalcp.Parameters
Bases:
TypedDictParameters 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()andSolver.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
WorkerParametersfor 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
WorkerParametersfor worker-specific parameters.
- 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:
Parameters.searchType: How workers are distributed across LNS, FDS, and FDSDualParameters.noOverlapPropagationLevel: Propagation strength for noOverlap constraintsParameters.cumulPropagationLevel: Propagation strength for cumulative constraints
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.searchTypeto use a specific search strategyAdjusting 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
Parameters.searchTypefor choosing the search algorithm.Parameters.noOverlapPropagationLevelfor tuning noOverlap propagation.Parameters.cumulPropagationLevelfor tuning cumulative propagation.
- 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
Parameters.presetfor automatic configuration of search and propagation.Parameters.noOverlapPropagationLevelwhich works well with FDS at higher levels.
- 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.nbWorkersis 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≥ 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 usingSolver.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
Parameters.timeLimitfor limiting solve time.
- 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.relativeGapToleranceas 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.absoluteGapToleranceas 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
Parameters.presetfor automatic configuration of propagation levels.Parameters.searchTypefor choosing the search algorithm.Model.no_overlap()for creating noOverlap constraints.
- 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
Parameters.presetfor automatic configuration of propagation levels.Parameters.searchTypefor choosing the search algorithm.Model.pulse()for creating pulse contributions to cumulative functions.
- 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 (seeParameters.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:
Geometric (the default): After each restart, restart limit is multiplied by
Parameters.fdsRestartGrowthFactor.Nested: Similar to Geometric but the limit is changed back to
Parameters.fdsInitialRestartLimiteach time a new maximum limit is reached.Luby: Luby restart strategy is used. Parameter
Parameters.fdsRestartGrowthFactoris ignored.
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.fdsRestartStrategyis 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.fdsInitialRestartLimitwhen 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.nbWorkersspecifies the number of workers regardless of the length of this list.See also
WorkerParametersfor the list of parameters that can be set per worker.
- 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, useParameters.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.colorparameter.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
Parameters.logLevelto control verbosity.Parameters.colorto override automatic color detection.
- 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
Solver.find_solver()for solver discovery logic.Parameters.solverArgsfor additional subprocess arguments.
- 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
Parameters.solverto specify a custom solver path.
- class optalcp.WorkerParameters
Bases:
TypedDictWorkerParameters specify the behavior of each worker separately. It is part of the
Parametersobject.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
Parameters.presetfor automatic configuration of search and propagation.Parameters.noOverlapPropagationLevelwhich works well with FDS at higher levels.
- 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.nbWorkersis 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
Parameters.presetfor automatic configuration of propagation levels.Parameters.searchTypefor choosing the search algorithm.Model.no_overlap()for creating noOverlap constraints.
- 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
Parameters.presetfor automatic configuration of propagation levels.Parameters.searchTypefor choosing the search algorithm.Model.pulse()for creating pulse contributions to cumulative functions.
- 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 (seeParameters.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:
Geometric (the default): After each restart, restart limit is multiplied by
Parameters.fdsRestartGrowthFactor.Nested: Similar to Geometric but the limit is changed back to
Parameters.fdsInitialRestartLimiteach time a new maximum limit is reached.Luby: Luby restart strategy is used. Parameter
Parameters.fdsRestartGrowthFactoris ignored.
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.fdsRestartStrategyis 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.fdsInitialRestartLimitwhen 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.
Results
- class optalcp.SolveResult
Bases:
objectThe result returned by
Model.solve()orSolver.solve().Contains comprehensive information about the solve:
Solution data:
SolveResult.solution: The best solution found (or None if no solution exists)SolveSummary.objective: The objective value of the best solutionSolveSummary.objective_bound: The proved bound on the objective
Solve statistics:
SolveSummary.nb_solutions: Total number of solutions foundSolveSummary.proof: Whether optimality or infeasibility was provedSolveSummary.duration: Total solve time in seconds
History tracking:
SolveResult.objective_history: When each improving solution was foundSolveResult.objective_bound_history: When each bound improvement was proved
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:
- Returns:
Solution count.
- property proof: bool
Whether the solve ended with a proof (optimality or infeasibility).
- Return type:
- Returns:
True if the solve completed with a proof.
Details
When True, the solver has either:
- Proved optimality (found a solution within the bounds defined by
Parameters.absoluteGapToleranceandParameters.relativeGapTolerance), or
Proved infeasibility (no solution exists)
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:
- Returns:
Seconds elapsed.
- property nb_branches: int
Total number of branches explored during the solve.
- Return type:
- Returns:
Branch count.
- property nb_fails: int
Total number of failures encountered during the solve.
- Return type:
- 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()orModel.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:
- Returns:
LNS step count.
- property nb_restarts: int
Total number of restarts performed during the solve.
- Return type:
- Returns:
Restart count.
- property nb_int_vars: int
Number of integer variables in the model.
- Return type:
- Returns:
Integer variable count.
- property nb_interval_vars: int
Number of interval variables in the model.
- Return type:
- Returns:
Interval variable count.
- property nb_constraints: int
Number of constraints in the model.
- Return type:
- Returns:
Constraint count.
- property solver: str
Solver name and version string.
- Return type:
- 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:
- Returns:
Worker count.
Details
This is the actual number of workers used by the solver, which may differ from the requested
Parameters.nbWorkersif 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:
- 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
ObjectiveEntryobjects, one for each solution found during the solve.Each entry contains:
ObjectiveEntry.solve_time: When the solution was foundObjectiveEntry.objective: The objective value of that solutionObjectiveEntry.valid: Whether the solution was verified
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
ObjectiveBoundEntryobjects, one for each bound improvement proved during the solve.Each entry contains:
ObjectiveBoundEntry.solve_time: When the bound was provedObjectiveBoundEntry.value: The bound value
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.verifySolutionsis 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:
objectSolution 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:
- 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:
- 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 | NoneGets 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 | NoneGets 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] | NoneGets an interval variable’s start and end from the solution.
- Parameters:
variable (IntervalVar) – The interval variable to get the value of
- Return type:
- 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(),SolverandSolver.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) -> NoneSets the value of the given integer variable in the solution.
Details
The variable will be present in the solution with the given value.
See also
Solution.set_absent()to make the variable absent.
Overload 2:
(variable: BoolVar, value: bool) -> NoneSets 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
Solution.set_absent()to make the variable absent.
Overload 3:
(variable: IntervalVar, start: int, end: int) -> NoneSets 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
Solution.set_absent()to make the variable absent.
- 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()andSolver.send_solution()).
- final class optalcp.SolveSummary
Bases:
objectSummary 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:
- Returns:
Solution count.
- property proof: bool
Whether the solve ended with a proof (optimality or infeasibility).
- Return type:
- Returns:
True if the solve completed with a proof.
Details
When True, the solver has either:
- Proved optimality (found a solution within the bounds defined by
Parameters.absoluteGapToleranceandParameters.relativeGapTolerance), or
Proved infeasibility (no solution exists)
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:
- Returns:
Seconds elapsed.
- property nb_branches: int
Total number of branches explored during the solve.
- Return type:
- Returns:
Branch count.
- property nb_fails: int
Total number of failures encountered during the solve.
- Return type:
- Returns:
Failure count.
- property nb_lns_steps: int
Total number of Large Neighborhood Search steps.
- Return type:
- Returns:
LNS step count.
- property nb_restarts: int
Total number of restarts performed during the solve.
- Return type:
- Returns:
Restart 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()orModel.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:
- Returns:
Integer variable count.
- property nb_interval_vars: int
Number of interval variables in the model.
- Return type:
- Returns:
Interval variable count.
- property nb_constraints: int
Number of constraints in the model.
- Return type:
- Returns:
Constraint count.
- property solver: str
Solver name and version string.
- Return type:
- 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:
- Returns:
Worker count.
Details
This is the actual number of workers used by the solver, which may differ from the requested
Parameters.nbWorkersif that parameter was not specified (auto-detect) or if the system has fewer cores than requested.
- final class optalcp.SolutionEvent
Bases:
objectAn event emitted when a solution is found.
This event is passed to the
Solver.on_solutioncallback 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
Solverfor the Python solver API.Solver.on_solutionto register a solution callback.
- 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
Solutionfor accessing variable values.
- valid: bool | None
Result of the verification of the solution.
When parameter
Parameters.verifySolutionsis 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.verifySolutionswas 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:
objectSingle 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
SolveResult.objective_historyfor accessing the history.
- solve_time: float
Duration of the solve when this solution was found, in seconds.
- Return type:
- 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:
No objective was specified in the model (no
Model.minimize()orModel.maximize()call).The objective expression has an absent value in this solution.
- 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.verifySolutionsis 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.verifySolutionswas 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:
objectSingle 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
SolveResult.objective_bound_historyfor accessing the history.
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