Skip to main content

Interval Variables

IntervalVar represents a scheduled task or activity with a start time, end time, and length. Interval variables are the fundamental building blocks for scheduling problems in OptalCP.

Creating Interval Variables

Create an interval variable using model.interval_var():

import optalcp as cp

model = cp.Model()

# Fixed interval: start=10, end=20, length=10
task1 = model.interval_var(start=10, end=20, name="task1")

# Variable start/end, fixed length
task2 = model.interval_var(length=15, name="task2")

# Restrict start and end ranges
task3 = model.interval_var(
start=(0, 100), # start in [0, 100]
end=(50, 200), # end in [50, 200]
name="task3"
)

# Variable length
task4 = model.interval_var(
length=(10, 30), # length in [10, 30]
name="task4"
)

Parameters

ParameterTypeDefaultDescription
startint or [min, max][0, IntervalMax]Start time or range
endint or [min, max][0, IntervalMax]End time or range
lengthint or [min, max][0, LengthMax]Duration or range
optionalboolfalseWhether interval can be absent
namestringauto-generatedIdentifier for debugging

Implicit Constraint

The solver enforces: length = end - start

This means you don't need to specify all three—the solver infers missing bounds.

Expressions for Constraints

Use these functions to get expressions for constraints and objectives:

# Expressions for start, end, length (IntExpr)
start_expr = task.start()
end_expr = task.end()
length_expr = task.length()

# Expression for presence (BoolExpr)
presence_expr = task.presence()

# Use in constraints
model.enforce(task1.end() <= task2.start())
model.minimize(task.end())

# Use presence in constraints
model.enforce(task1.presence() == task2.presence())

Expression Functions

FunctionReturnsDescription
start()IntExprStart time of the interval
end()IntExprEnd time of the interval
length()IntExprDuration of the interval
presence()BoolExprTrue if present, false if absent

Optional Intervals

Intervals can be optional, meaning they may or may not appear in the solution:

# Create optional interval
task_a = model.interval_var(length=10, optional=True, name="task_a")
task_b = model.interval_var(length=20, optional=True, name="task_b")

# Link presence: both present or both absent
model.enforce(task_a.presence() == task_b.presence())

Absent Semantics

When an optional interval is absent in a solution:

  1. Expressions involving the interval become absent: Operations on absent intervals propagate the absent value
  2. Precedence constraints are satisfied trivially: If either interval is absent, the constraint is automatically satisfied
  3. Resource usage is zero: Absent intervals do not consume resources
# Example: optional maintenance task
machine_a = model.interval_var(length=100, name="machine_a")
maintenance = model.interval_var(
length=30,
optional=True,
name="maintenance"
)

# If maintenance is present, it must be after machine_a
# Maintenance absence doesn't violate the precedence
machine_a.end_before_start(maintenance)

Querying and Modifying Bounds

Each interval variable has a domain—the set of possible values defined in the model. The domain includes bounds for start, end, and length, plus whether the interval can be present or absent.

These properties let you query and modify the domain during model construction:

task = model.interval_var(length=10, optional=True, name="task")

# Presence status (check and set using `optional` property)
task.optional == True # True: can be present or absent in solution
task.optional == False # True: fixed to be present
task.optional is None # True: fixed to be absent
task.optional = True # Allow both present and absent
task.optional = False # Fix to be present
task.optional = None # Fix to be absent

# Time bounds (use properties with snake_case)
task.start_min # Query minimum start
task.start_max # Query maximum start
task.start_min = 0 # Set minimum start
task.start_max = 100 # Set maximum start

# Similarly for end and length:
# task.end_min, task.end_max, task.length_min, task.length_max

Constants

OptalCP defines constants for interval bounds:

import optalcp as cp

# Constants for interval bounds
cp.IntervalMin # -715827882 (minimum start/end value)
cp.IntervalMax # 715827882 (maximum start/end value)
cp.LengthMax # 1431655764 (maximum length = IntervalMax - IntervalMin)

# Example usage
task = model.interval_var(
start=(0, cp.IntervalMax),
length=(1, cp.LengthMax),
name="unbounded_task"
)

Reading Solution Values

After solving, read actual values from the solution (not the domain):

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

# Read interval values
start = sol.get_start(task) # Actual start time
end = sol.get_end(task) # Actual end time
length = sol.get_length(task) # Actual length

# Check presence (for optional intervals)
if sol.is_present(task):
print(f"Task scheduled at {start}-{end}")
else:
print("Task is absent")
Common Mistakes with Presence

Don't confuse presence() (expression for constraints) with the optional property (queries presence status during modeling):

# WRONG: optional property returns bool/None, evaluates immediately
model.enforce(a.optional == b.optional) # Compiles but wrong!

# CORRECT: presence() returns BoolExpr for constraints
model.enforce(a.presence() == b.presence())

# WRONG: optional property checks if fixed, not solution value
if task.optional == False: # True only if interval cannot be absent
print("scheduled")

# CORRECT: check the actual solution
if solution.is_present(task):
print("scheduled")

Complete Example

import optalcp as cp

model = cp.Model()

# Create tasks with dependencies
cut = model.interval_var(length=30, name="cut")
sand = model.interval_var(length=20, name="sand")
assemble = model.interval_var(length=45, name="assemble")

# Precedence constraints
cut.end_before_start(sand)
sand.end_before_start(assemble)

# Minimize completion time
model.minimize(assemble.end())

# Solve
result = model.solve()
if result.solution:
print(f"Cut: {result.solution.get_start(cut)}-{result.solution.get_end(cut)}")
print(f"Sand: {result.solution.get_start(sand)}-{result.solution.get_end(sand)}")
print(f"Assemble: {result.solution.get_start(assemble)}-{result.solution.get_end(assemble)}")
print(f"Makespan: {result.objective}")

See Also

  • Constraints - Precedence functions and constraint types
  • Variables - IntVar and BoolVar for auxiliary decisions
  • Expressions - Using interval properties in expressions
  • Alternative - Choosing between optional interval variants
  • Span - Grouping intervals with a parent interval