Skip to main content

Integer and Boolean Variables

While OptalCP excels at scheduling with interval variables, IntVar and BoolVar let you model decisions beyond time: resource levels, machine assignments, selection choices, counts, and general integer optimization problems.

Common uses in scheduling:

  • Variable resource demands (task needs 1-3 workers)
  • Machine or mode selection
  • Counting selected optional tasks
  • Linking interval presence to other decisions

Non-scheduling problems: OptalCP can solve any problem expressible with integer variables, boolean logic, and arithmetic constraints—assignment, packing, allocation, etc. OptalCP is optimized for scheduling and does not include specialized global constraints like all-different or circuit.

Creating Integer Variables

import optalcp as cp

model = cp.Model()

# Variable in range [0, 100]
x = model.int_var(min=0, max=100, name="x")

# Variable with default bounds [0, IntVarMax]
y = model.int_var(name="y")

# Optional integer variable (can be absent)
opt = model.int_var(min=0, max=50, optional=True, name="opt")

Parameters

ParameterTypeDefaultDescription
minint0Minimum value
maxintIntVarMaxMaximum value
optionalboolfalseWhether variable can be absent
namestringauto-generatedIdentifier for debugging

Creating Boolean Variables

# Simple boolean variable
is_selected = model.bool_var(name="is_selected")

# Optional boolean variable
opt_flag = model.bool_var(optional=True, name="opt_flag")

Parameters

ParameterTypeDefaultDescription
optionalboolfalseWhether variable can be absent
namestringauto-generatedIdentifier for debugging

Using Variables in Constraints

Variables can be used in arithmetic expressions and constraints:

x = model.int_var(min=0, max=100, name="x")
y = model.int_var(min=0, max=100, name="y")
b = model.bool_var(name="b")

# Arithmetic constraints
model.enforce(x + y <= 50)
model.enforce(x * 2 == y)

# Boolean in constraints
model.enforce(b == (x > 10)) # b is true iff x > 10

# Variable resource demand
task = model.interval_var(length=10, name="task")
demand = model.int_var(min=1, max=3, name="demand")
model.enforce(model.sum([model.pulse(task, demand)]) <= 5)

# Minimize/maximize
model.minimize(x + y)
Pitfall: Don't Create Variables for Expressions

Don't create variables to "store" expressions—expressions can be reused directly multiple times.

# DON'T: Create a variable to store an expression
the_end = model.int_var(min=0, max=1000, name="the_end")
model.enforce(the_end == task1.end())
model.enforce(the_end <= task2.start())
model.enforce(the_end <= task3.start())

# DO: Use the expression directly
model.enforce(task1.end() <= task2.start())
model.enforce(task1.end() <= task3.start())

# DON'T: Create a boolean variable for presence
overtime_needed = model.bool_var(name="overtime_needed")
model.enforce(overtime_needed == overtime.presence())

# DO: Use presence() directly
model.enforce(overtime.presence().implies(manager.presence()))

# This also applies to composed expressions
# DON'T:
total_length = model.int_var(min=0, max=1000, name="total_length")
model.enforce(total_length == task1.length() + task2.length())

# DO:
model.minimize(task1.length() + task2.length())

# Common case: lateness/tardiness
# DON'T:
deadline = 100
lateness = model.int_var(min=0, max=100, name="lateness")
model.enforce(task.end() <= deadline + lateness)
model.minimize(lateness * 10)

# DO:
deadline = 100
lateness = (task.end() - deadline).max2(0) # Python variable, not model variable
model.minimize(lateness * 10)

Storing an expression in a Python/JavaScript variable is fine—it's just a reference to the expression, not a new solver variable. The problem is model.intVar()/model.int_var() which creates a solver variable that must be assigned a value during search.

The solver handles this better because:

  • Expression recognition: The solver detects when the same expression appears multiple times and can optimize accordingly.
  • Automatic auxiliary variables: The solver creates internal auxiliary variables only when beneficial—some constraints may be eliminated during presolve or specialized (e.g., into precedence constraints).
  • Better search: User-created variables are branching candidates. Auxiliary variables the solver creates are "derived" and not branched on, leading to more efficient search.
  • Preserves problem structure: User-created "alias" variables hide the mathematical structure of the problem, potentially confusing search heuristics.

Optional Variables

Like intervals, integer and boolean variables can be optional. This is especially useful for variable-height pulses over optional intervals—when the interval is absent, its resource demand should also be absent.

# Optional task with optional variable demand
task = model.interval_var(length=10, optional=True, name="task")
demand = model.int_var(min=1, max=3, optional=True, name="demand")

# Synchronize presence: both present or both absent
model.enforce(demand.presence() == task.presence())

# Use in cumulative constraint
model.enforce(model.sum([model.pulse(task, demand)]) <= 5)

When an optional variable is absent:

  • Expressions involving the variable become absent
  • The variable has no value in the solution

Querying and Modifying Bounds

Each variable has a domain—the set of possible values defined in the model. These properties let you query and modify the domain during model construction:

x = model.int_var(min=0, max=100, optional=True, name="x")

# Presence status
x.optional == True # True: can be present or absent in solution
x.optional == False # True: fixed to be present
x.optional is None # True: fixed to be absent
x.optional = True # Allow both present and absent
x.optional = False # Fix to be present
x.optional = None # Fix to be absent

# Value bounds
x.min # Query minimum value
x.max # Query maximum value
x.min = 10 # Set minimum value
x.max = 90 # Set maximum value

Constants

import optalcp as cp

cp.IntVarMin # -1073741823 (minimum for IntVar)
cp.IntVarMax # 1073741823 (maximum for IntVar)

Reading Solution Values

x = model.int_var(min=0, max=100, name="x")
b = model.bool_var(name="b")

result = model.solve()
if result.solution:
sol = result.solution

# Read values
x_val = sol.get_value(x) # int or None if absent
b_val = sol.get_value(b) # bool or None if absent

# Check presence (for optional variables)
if sol.is_present(x):
print(f"x = {x_val}")
else:
print("x is absent")

Complete Example

import optalcp as cp

model = cp.Model()

# Task with variable resource requirement
task = model.interval_var(length=10, name="task")
demand = model.int_var(min=1, max=3, name="demand")

# Resource capacity constraint
model.enforce(model.sum([model.pulse(task, demand)]) <= 5)

# Minimize resource usage
model.minimize(demand)

result = model.solve()
if result.solution:
print(f"Task: {result.solution.get_start(task)}-{result.solution.get_end(task)}")
print(f"Demand: {result.solution.get_value(demand)}")

See Also