Expressions
IntExpr and BoolExpr represent computed values derived from variables. They are used to build constraints, objectives, and derived quantities.
Overview
| Type | Represents | Sources |
|---|---|---|
| IntExpr | Integer value | IntVar, IntervalVar.start/end/length(), arithmetic on expressions |
| BoolExpr | Boolean value | BoolVar, IntervalVar.presence(), comparisons, boolean operations |
- Python
- TypeScript
import optalcp as cp
model = cp.Model()
task = model.interval_var(length=10, name="task")
x = model.int_var(min=0, max=100, name="x")
b = model.bool_var(name="b")
# IntExpr sources
start = task.start() # From interval
end = task.end()
length = task.length()
expr = x + 10 # From arithmetic
# BoolExpr sources
present = task.presence() # From interval
cond = x > 50 # From comparison
flag = b | (x < 10) # From boolean ops
import * as CP from '@scheduleopt/optalcp';
const model = new CP.Model();
const task = model.intervalVar({ length: 10, name: "task" });
const x = model.intVar({ min: 0, max: 100, name: "x" });
const b = model.boolVar({ name: "b" });
// IntExpr sources
const start = task.start(); // From interval
const end = task.end();
const length = task.length();
const expr = x.plus(10); // From arithmetic
// BoolExpr sources
const present = task.presence(); // From interval
const cond = x.gt(50); // From comparison
const flag = b.or(x.lt(10)); // From boolean ops
Operators
Arithmetic (IntExpr)
- Python
- TypeScript
# Binary operators
x + y # Addition
x - y # Subtraction
x * 2 # Multiplication (by constant)
x // 2 # Integer division (by constant)
# Unary operators
-x # Negation
abs(x - y) # Absolute value
// Binary operators
x.plus(y) // Addition
x.minus(y) // Subtraction
x.times(2) // Multiplication (by constant)
x.div(2) // Integer division (by constant)
// Unary operators
x.neg() // Negation
x.minus(y).abs() // Absolute value
Comparison (IntExpr → BoolExpr)
- Python
- TypeScript
x < y # Less than
x <= y # Less than or equal
x == y # Equal
x != y # Not equal
x >= y # Greater than or equal
x > y # Greater than
# Use in constraints
model.enforce(task.end() <= 100)
model.enforce(x + y == 50)
# IMPORTANT: Python's operator precedence requires parentheses here
b = model.bool_var(name="b")
model.enforce(b == (x > 10)) # Parentheses required!
# Without: b == x > 10 parses as (b == x) > 10
# This is standard Python behavior, not specific to OptalCP
x.lt(y) // Less than
x.le(y) // Less than or equal
x.eq(y) // Equal
x.ne(y) // Not equal
x.ge(y) // Greater than or equal
x.gt(y) // Greater than
// Use in constraints
model.enforce(task.end().le(100));
model.enforce(x.plus(y).eq(50));
const b = model.boolVar({ name: "b" });
model.enforce(b.eq(x.gt(10)));
Boolean (BoolExpr)
- Python
- TypeScript
~b # NOT
b1 | b2 # OR
b1 & b2 # AND
b1.implies(b2) # Implication (equivalent to ~b1 | b2)
# Use in constraints
model.enforce(b1 | b2) # At least one
model.enforce(~(b1 & b2)) # Not both
model.enforce(b1.implies(b2)) # If b1 then b2
b.not() // NOT
b1.or(b2) // OR
b1.and(b2) // AND
b1.implies(b2) // Implication (equivalent to b1.not().or(b2))
// Use in constraints
model.enforce(b1.or(b2)); // At least one
model.enforce(b1.and(b2).not()); // Not both
model.enforce(b1.implies(b2)); // If b1 then b2
Absent Value Propagation
When an optional variable is absent, most expressions involving it become absent—but there are important exceptions.
- Python
- TypeScript
task = model.interval_var(length=10, optional=True, name="task")
# If task is absent:
task.end() # absent
task.end() + 10 # absent (arithmetic propagates)
task.end() + 10 <= 100 # absent
# Exceptions - these are NOT absent:
task.presence() # false (not absent!)
task.end().guard(0) # 0 (guard provides default)
model.sum([task.end(), 5]) # 5 (aggregations skip absent)
model.max([task.end(), 5]) # 5 (aggregations skip absent)
model.identity(task.end(), other) # true or false (never absent)
const task = model.intervalVar({ length: 10, optional: true, name: "task" });
// If task is absent:
task.end() // absent
task.end().plus(10) // absent (arithmetic propagates)
task.end().plus(10).le(100) // absent
// Exceptions - these are NOT absent:
task.presence() // false (not absent!)
task.end().guard(0) // 0 (guard provides default)
model.sum([task.end(), 5]) // 5 (aggregations skip absent)
model.max([task.end(), 5]) // 5 (aggregations skip absent)
model.identity(task.end(), other) // true or false (never absent)
Arithmetic and aggregation handle absent values differently:
- Python
- TypeScript
# x is absent
x + 5 # absent (arithmetic propagates)
model.sum([x, 5]) # 5 (aggregation skips absent)
// x is absent
x.plus(5) // absent (arithmetic propagates)
model.sum([x, 5]) // 5 (aggregation skips absent)
See Enforcing BoolExpr Constraints for how absent values affect constraint satisfaction.
Guard: Default for Absent
guard() provides a default value when an expression is absent:
- Python
- TypeScript
task = model.interval_var(length=10, optional=True, name="task")
# If task is absent, use 0 instead of absent
guarded_end = task.end().guard(0)
# Useful in objectives: absent tasks contribute 0
model.minimize(task.end().guard(0))
# Useful in arithmetic (where absent would propagate)
total = task1.end().guard(0) + task2.end().guard(0)
const task = model.intervalVar({ length: 10, optional: true, name: "task" });
// If task is absent, use 0 instead of absent
const guardedEnd = task.end().guard(0);
// Useful in objectives: absent tasks contribute 0
model.minimize(task.end().guard(0));
// Useful in arithmetic (where absent would propagate)
const total = task1.end().guard(0).plus(task2.end().guard(0));
Identity: Equality Including Presence
identity() constrains two expressions to have the same value and the same presence status:
- Python
- TypeScript
# Equality vs identity:
# - x == 0 is absent when x is absent (constraint satisfied)
# - identity(x, 0) is false when x is absent (constraint violated)
opt = model.int_var(min=0, max=10, optional=True, name="opt")
model.identity(opt, other) # Same value AND same presence
// Equality vs identity:
// - x.eq(0) is absent when x is absent (constraint satisfied)
// - model.identity(x, 0) is false when x is absent (constraint violated)
const opt = model.intVar({ min: 0, max: 10, optional: true, name: "opt" });
model.identity(opt, other); // Same value AND same presence
Aggregation
- Python
- TypeScript
tasks = [model.interval_var(length=10, optional=True) for _ in range(5)]
# Sum of present values (absent excluded)
total_length = model.sum([t.length() for t in tasks])
# Maximum end time (makespan)
makespan = model.max([t.end() for t in tasks])
# Minimum start time
earliest = model.min([t.start() for t in tasks])
const tasks = Array.from({ length: 5 }, () =>
model.intervalVar({ length: 10, optional: true })
);
// Sum of present values (absent excluded)
const totalLength = model.sum(tasks.map(t => t.length()));
// Maximum end time (makespan)
const makespan = model.max(tasks.map(t => t.end()));
// Minimum start time
const earliest = model.min(tasks.map(t => t.start()));
Absent handling in aggregations: Absent values are skipped. Edge cases:
sum([])or all absent → 0min([])or all absent → absentmax([])or all absent → absent
BoolExpr as IntExpr
BoolExpr is also an IntExpr with value 1 for true and 0 for false. This enables counting and arithmetic with booleans:
- Python
- TypeScript
b1 = model.bool_var(name="b1")
b2 = model.bool_var(name="b2")
b3 = model.bool_var(name="b3")
# Count true values
count = b1 + b2 + b3 # 0 to 3
# Weighted sum
cost = b1 * 100 + b2 * 50 + b3 * 25
# At-least-k constraints
model.enforce(b1 + b2 + b3 >= 2) # At least 2 true
# At-most-k constraints
model.enforce(b1 + b2 + b3 <= 1) # At most 1 true
# Count present optional tasks
tasks = [model.interval_var(length=10, optional=True) for _ in range(5)]
num_present = model.sum([t.presence() for t in tasks])
model.enforce(num_present >= 3) # At least 3 tasks scheduled
const b1 = model.boolVar({ name: "b1" });
const b2 = model.boolVar({ name: "b2" });
const b3 = model.boolVar({ name: "b3" });
// Count true values
const count = b1.plus(b2).plus(b3); // 0 to 3
// Weighted sum
const cost = b1.times(100).plus(b2.times(50)).plus(b3.times(25));
// At-least-k constraints
model.enforce(b1.plus(b2).plus(b3).ge(2)); // At least 2 true
// At-most-k constraints
model.enforce(b1.plus(b2).plus(b3).le(1)); // At most 1 true
// Count present optional tasks
const tasks = Array.from({ length: 5 }, () =>
model.intervalVar({ length: 10, optional: true })
);
const numPresent = model.sum(tasks.map(t => t.presence()));
model.enforce(numPresent.ge(3)); // At least 3 tasks scheduled
Objectives
Any IntExpr can be used as an objective:
- Python
- TypeScript
task = model.interval_var(length=10, name="task")
# Two equivalent ways to set objective
model.minimize(task.end())
task.end().minimize() # Fluent style
# Maximize
model.maximize(task.start())
task.start().maximize() # Fluent style
# Complex objectives
makespan = model.max([t.end() for t in tasks])
total_cost = model.sum([t.length() * cost[i] for i, t in enumerate(tasks)])
model.minimize(makespan * 1000 + total_cost) # Weighted combination
const task = model.intervalVar({ length: 10, name: "task" });
// Two equivalent ways to set objective
model.minimize(task.end());
task.end().minimize(); // Fluent style
// Maximize
model.maximize(task.start());
task.start().maximize(); // Fluent style
// Complex objectives
const makespan = model.max(tasks.map(t => t.end()));
const totalCost = model.sum(tasks.map((t, i) => t.length().times(cost[i])));
model.minimize(makespan.times(1000).plus(totalCost)); // Weighted combination
Complete Example
- Python
- TypeScript
import optalcp as cp
model = cp.Model()
# Optional tasks with different lengths and values
tasks = [
model.interval_var(length=10 + i * 5, optional=True, name=f"task_{i}")
for i in range(5)
]
values = [50, 30, 40, 20, 60] # Value of each task
# Tasks cannot overlap (those that are present)
model.no_overlap([t for t in tasks])
# At least 3 tasks must be scheduled
num_present = model.sum([t.presence() for t in tasks])
model.enforce(num_present >= 3)
# Deadline: all tasks must finish by time 50
for task in tasks:
model.enforce(task.end() <= 50)
# Maximize total value of scheduled tasks
total_value = model.sum([t.presence() * values[i] for i, t in enumerate(tasks)])
model.maximize(total_value)
result = model.solve()
if result.solution:
for task in tasks:
if result.solution.is_present(task):
start = result.solution.get_start(task)
end = result.solution.get_end(task)
print(f"{task.name}: {start}-{end}")
else:
print(f"{task.name}: absent")
print(f"Total value: {result.objective}")
import * as CP from '@scheduleopt/optalcp';
const model = new CP.Model();
// Optional tasks with different lengths and values
const tasks = Array.from({ length: 5 }, (_, i) =>
model.intervalVar({ length: 10 + i * 5, optional: true, name: `task_${i}` })
);
const values = [50, 30, 40, 20, 60]; // Value of each task
// Tasks cannot overlap (those that are present)
model.noOverlap(tasks);
// At least 3 tasks must be scheduled
const numPresent = model.sum(tasks.map(t => t.presence()));
model.enforce(numPresent.ge(3));
// Deadline: all tasks must finish by time 50
for (const task of tasks) {
model.enforce(task.end().le(50));
}
// Maximize total value of scheduled tasks
const totalValue = model.sum(tasks.map((t, i) => t.presence().times(values[i])));
model.maximize(totalValue);
const result = await model.solve();
if (result.solution) {
for (const task of tasks) {
if (result.solution.isPresent(task)) {
const start = result.solution.getStart(task);
const end = result.solution.getEnd(task);
console.log(`${task.name}: ${start}-${end}`);
} else {
console.log(`${task.name}: absent`);
}
}
console.log(`Total value: ${result.objective}`);
}
See Also
- Variables - IntVar and BoolVar basics
- Intervals - IntervalVar expressions (start, end, length, presence)
- Constraints - Using expressions in constraints