Objectives
An objective function defines what the solver should optimize. OptalCP supports minimization and maximization of integer expressions.
Creating Objectives
Create an objective using model.minimize() or model.maximize():
- Python
- TypeScript
import optalcp as cp
model = cp.Model()
task = model.interval_var(length=10, name="task")
# Minimize completion time
model.minimize(task.end())
# Maximize start time
model.maximize(task.start())
import * as CP from '@scheduleopt/optalcp';
const model = new CP.Model();
const task = model.intervalVar({ length: 10, name: "task" });
// Minimize completion time
model.minimize(task.end());
// Maximize start time
model.maximize(task.start());
Shortcut Syntax
Integer expressions provide shortcut functions:
- Python
- TypeScript
# These are equivalent:
task.end().minimize()
model.minimize(task.end())
# These are equivalent:
task.start().maximize()
model.maximize(task.start())
// These are equivalent:
task.end().minimize();
model.minimize(task.end());
// These are equivalent:
task.start().maximize();
model.maximize(task.start());
Single Objective
Only one objective is allowed per model. Setting a new objective replaces the previous one:
- Python
- TypeScript
model.minimize(task1.end()) # First objective
model.minimize(task2.end()) # Replaces first objective
model.minimize(task1.end()); // First objective
model.minimize(task2.end()); // Replaces first objective
Minimize or maximize expressions directly—don't create intermediate variables.
- Python
- TypeScript
tasks = [model.interval_var(length=10) for _ in range(5)]
# DON'T: Create a variable and constrain it to equal the expression
makespan = model.int_var(min=0, max=1000, name="makespan")
model.enforce(makespan == model.max([t.end() for t in tasks]))
model.minimize(makespan)
# DO: Minimize the expression directly
model.minimize(model.max([t.end() for t in tasks]))
# ESPECIALLY DON'T: Use upper-bound constraints
makespan = model.int_var(min=0, max=1000, name="makespan")
model.minimize(makespan)
for t in tasks:
model.enforce(t.end() <= makespan) # Hides the max() structure!
const tasks = Array.from({ length: 5 }, () => model.intervalVar({ length: 10 }));
// DON'T: Create a variable and constrain it to equal the expression
const makespan = model.intVar({ min: 0, max: 1000, name: "makespan" });
model.enforce(makespan.eq(model.max(tasks.map(t => t.end()))));
model.minimize(makespan);
// DO: Minimize the expression directly
model.minimize(model.max(tasks.map(t => t.end())));
// ESPECIALLY DON'T: Use upper-bound constraints
const makespan2 = model.intVar({ min: 0, max: 1000, name: "makespan" });
model.minimize(makespan2);
for (const t of tasks) {
model.enforce(t.end().le(makespan2)); // Hides the max() structure!
}
The problems with explicit variables:
- Needless branching: The solver will branch on user-created variables, wasting search effort on a derived value.
- Hidden structure: Using
t.end() <= makespanconstraints hides the fact that the objective is the maximum of task ends. The solver cannot apply specialized propagation formax. - Weaker bounds: The solver computes tighter bounds when it knows the objective structure directly.
See also Don't Create Variables for Expressions for the general principle.
Common Objective Patterns
Makespan
Minimize the completion time of all tasks:
- Python
- TypeScript
tasks = [
model.interval_var(length=10, name=f"task_{i}")
for i in range(5)
]
# Makespan: latest end time
makespan = model.max([t.end() for t in tasks])
model.minimize(makespan)
const tasks = Array.from({ length: 5 }, (_, i) =>
model.intervalVar({ length: 10, name: `task_${i}` })
);
// Makespan: latest end time
const makespan = model.max(tasks.map(t => t.end()));
model.minimize(makespan);
Total Flow Time
Sum of completion times:
- Python
- TypeScript
tasks = [model.interval_var(length=10) for _ in range(5)]
# Total flow time
total_flow = model.sum([t.end() for t in tasks])
model.minimize(total_flow)
const tasks = Array.from({ length: 5 }, () => model.intervalVar({ length: 10 }));
// Total flow time
const totalFlow = model.sum(tasks.map(t => t.end()));
model.minimize(totalFlow);
Weighted Sum
Combine multiple criteria with weights:
- Python
- TypeScript
tasks = [model.interval_var(length=10) for _ in range(5)]
weights = [5, 3, 2, 4, 1]
# Weighted completion time
weighted_sum = model.sum([
t.end() * w for t, w in zip(tasks, weights)
])
model.minimize(weighted_sum)
const tasks = Array.from({ length: 5 }, () => model.intervalVar({ length: 10 }));
const weights = [5, 3, 2, 4, 1];
// Weighted completion time
const weightedSum = model.sum(
tasks.map((t, i) => t.end().times(weights[i]))
);
model.minimize(weightedSum);
Tardiness
Minimize lateness with respect to deadlines:
- Python
- TypeScript
tasks = [model.interval_var(length=10) for _ in range(5)]
deadlines = [100, 150, 120, 180, 200]
# Tardiness: max(0, completion - deadline)
tardiness = [
model.max2(0, task.end() - deadline)
for task, deadline in zip(tasks, deadlines)
]
total_tardiness = model.sum(tardiness)
model.minimize(total_tardiness)
const tasks = Array.from({ length: 5 }, () => model.intervalVar({ length: 10 }));
const deadlines = [100, 150, 120, 180, 200];
// Tardiness: max(0, completion - deadline)
const tardiness = tasks.map((task, i) =>
model.max2(0, task.end().minus(deadlines[i]))
);
const totalTardiness = model.sum(tardiness);
model.minimize(totalTardiness);
Number of Tasks
Maximize the number of selected optional tasks:
- Python
- TypeScript
tasks = [
model.interval_var(length=10, optional=True)
for _ in range(5)
]
# Count present tasks
num_selected = model.sum([t.presence() for t in tasks])
model.maximize(num_selected)
const tasks = Array.from({ length: 5 }, () =>
model.intervalVar({ length: 10, optional: true })
);
// Count present tasks
const numSelected = model.sum(tasks.map(t => t.presence()));
model.maximize(numSelected);
Multi-Objective Optimization
OptalCP does not support native multi-objective optimization, but you can combine objectives using weighted sums or lexicographic optimization:
Weighted Sum Approach
Combine objectives with weights:
- Python
- TypeScript
tasks = [model.interval_var(length=10) for _ in range(5)]
# Makespan
makespan = model.max([t.end() for t in tasks])
# Total flow time
total_flow = model.sum([t.end() for t in tasks])
# Weighted combination
objective = makespan * 10 + total_flow
model.minimize(objective)
const tasks = Array.from({ length: 5 }, () => model.intervalVar({ length: 10 }));
// Makespan
const makespan = model.max(tasks.map(t => t.end()));
// Total flow time
const totalFlow = model.sum(tasks.map(t => t.end()));
// Weighted combination
const objective = makespan.times(10).plus(totalFlow);
model.minimize(objective);
Lexicographic Optimization
Optimize objectives in order of priority:
- Python
- TypeScript
# Step 1: Optimize primary objective
model.minimize(makespan)
result1 = model.solve()
if result1.solution:
# Step 2: Fix primary objective, optimize secondary
optimal_makespan = result1.objective
model.enforce(makespan <= optimal_makespan)
model.minimize(total_flow)
result2 = model.solve()
// Step 1: Optimize primary objective
model.minimize(makespan);
const result1 = await model.solve();
if (result1.solution) {
// Step 2: Fix primary objective, optimize secondary
const optimalMakespan = result1.objective!;
model.enforce(makespan.le(optimalMakespan));
model.minimize(totalFlow);
const result2 = await model.solve();
}
Absent Expressions in Objectives
Aggregations like sum, max, and min skip absent values. For example, max([absent, 5, absent]) equals 5. However, if the entire objective expression is absent, it is treated as the worst possible value: +∞ for minimization, −∞ for maximization.
This can happen when:
- Minimizing a single optional interval's end:
minimize(task.end())wheretaskis absent - Taking
maxorminof an empty set (all values are absent)
Use guard() to provide an explicit default:
- Python
- TypeScript
task = model.interval_var(length=10, optional=True)
# If task is absent, end() is absent → worst value for minimization
model.minimize(task.end())
# With guard: if task is absent, use 0 instead
model.minimize(task.end().guard(0))
const task = model.intervalVar({ length: 10, optional: true });
// If task is absent, end() is absent → worst value for minimization
model.minimize(task.end());
// With guard: if task is absent, use 0 instead
model.minimize(task.end().guard(0));
Satisfaction Problems
For satisfaction problems (finding any feasible solution without optimization), simply omit the objective. The solver automatically stops after finding the first feasible solution.
- Python
- TypeScript
model = cp.Model()
# Add variables and constraints
# ...
# No objective - solver automatically stops after first solution
result = model.solve()
const model = new CP.Model();
// Add variables and constraints
// ...
// No objective - solver automatically stops after first solution
const result = await model.solve();
To find multiple solutions, explicitly set solutionLimit:
- Python
- TypeScript
# Find up to 5 different solutions
result = model.solve({'solutionLimit': 5})
// Find up to 5 different solutions
const result = await model.solve({ solutionLimit: 5 });
Reading Objective Values
- Python
- TypeScript
result = model.solve()
if result.solution:
# Objective value
obj_value = result.objective
print(f"Objective: {obj_value}")
# Objective sense
print(f"Sense: {result.objective_sense}") # 'minimize' or 'maximize'
# Best bound (for optimization)
if result.objective_bound is not None:
print(f"Bound: {result.objective_bound}")
gap = abs(result.objective - result.objective_bound)
print(f"Gap: {gap}")
const result = await model.solve();
if (result.solution) {
// Objective value
const objValue = result.objective;
console.log(`Objective: ${objValue}`);
// Objective sense
console.log(`Sense: ${result.objectiveSense}`); // 'minimize' or 'maximize'
// Best bound (for optimization)
if (result.objectiveBound !== undefined) {
console.log(`Bound: ${result.objectiveBound}`);
const gap = Math.abs(result.objective! - result.objectiveBound);
console.log(`Gap: ${gap}`);
}
}
Complete Example
- Python
- TypeScript
import optalcp as cp
model = cp.Model()
# Job shop scheduling: 3 jobs, 2 machines
jobs = []
for job_id in range(3):
job = []
for op_id in range(2):
task = model.interval_var(
length=10 + job_id * 5 + op_id * 3,
name=f"job{job_id}_op{op_id}"
)
job.append(task)
jobs.append(job)
# Operations in sequence
job[0].end_before_start(job[1])
# Machine constraints
machine1_tasks = [jobs[i][0] for i in range(3)]
machine2_tasks = [jobs[i][1] for i in range(3)]
model.no_overlap(machine1_tasks)
model.no_overlap(machine2_tasks)
# Objective: minimize makespan
makespan = model.max([jobs[i][1].end() for i in range(3)])
model.minimize(makespan)
# Solve
result = model.solve()
if result.solution:
print(f"Makespan: {result.objective}")
for i, job in enumerate(jobs):
for j, task in enumerate(job):
start = result.solution.get_start(task)
end = result.solution.get_end(task)
print(f"Job {i} Op {j}: {start}-{end}")
import * as CP from '@scheduleopt/optalcp';
const model = new CP.Model();
// Job shop scheduling: 3 jobs, 2 machines
const jobs = [];
for (let jobId = 0; jobId < 3; jobId++) {
const job = [];
for (let opId = 0; opId < 2; opId++) {
const task = model.intervalVar({
length: 10 + jobId * 5 + opId * 3,
name: `job${jobId}_op${opId}`
});
job.push(task);
}
jobs.push(job);
// Operations in sequence
job[0].endBeforeStart(job[1]);
}
// Machine constraints
const machine1Tasks = jobs.map(job => job[0]);
const machine2Tasks = jobs.map(job => job[1]);
model.noOverlap(machine1Tasks);
model.noOverlap(machine2Tasks);
// Objective: minimize makespan
const makespan = model.max(jobs.map(job => job[1].end()));
model.minimize(makespan);
// Solve
const result = await model.solve();
if (result.solution) {
console.log(`Makespan: ${result.objective}`);
jobs.forEach((job, i) => {
job.forEach((task, j) => {
const start = result.solution.getStart(task);
const end = result.solution.getEnd(task);
console.log(`Job ${i} Op ${j}: ${start}-${end}`);
});
});
}
See Also
- Expressions - Building expressions for objectives
- Solving/Basics - Running the solver and interpreting results
- Solving/Solutions - Reading objective values from solutions