Resource Types Overview
OptalCP has three types of resource constraints for modeling different scheduling scenarios. Each type serves distinct use cases and has different semantics.
Resource Type Comparison
Disjunctive Resources (No Overlap)
Use case: Resources that can execute only one task at a time.
Constraint: no_overlap(intervals)
Examples:
- Single machine or tool (saw, oven, robot)
- Meeting room
- Single-threaded processor
- Any resource with capacity = 1
Key features:
- Binary occupancy (free or busy)
- Tasks cannot overlap in time
- Supports sequence variables for ordering control
- Supports transition times between tasks
Cumulative Resources
Use case: Resources with limited capacity that can handle multiple tasks simultaneously.
Constraint: sum(pulse expressions) <= capacity
Examples:
- Workers or staff (e.g., 5 workers available)
- Memory or storage (e.g., 16 GB RAM)
- Bandwidth (e.g., 100 Mbps network)
- Parallel processing units
Key features:
- Integer capacity constraint (can be variable or fixed)
- Multiple tasks can execute simultaneously if total demand ≤ capacity
- Variable or fixed demand per task
Reservoir Resources
Use case: Resources that are produced and consumed over time, with level tracking.
Constraint: level >= min_level and level <= max_level
Examples:
- Inventory (parts produced and consumed)
- Fuel or energy (generated and used)
- Budget or credits (earned and spent)
- Buffer or queue (items added and removed)
Key features:
- Level changes in instantaneous steps (not continuous)
- Production increases level (positive steps)
- Consumption decreases level (negative steps)
- Level bounds enforced throughout horizon
- Initial level can be specified
Comparison Table
| Feature | Disjunctive | Cumulative | Reservoir |
|---|---|---|---|
| Capacity | Binary (0 or 1) | Integer (0 to N) | Level with step changes |
| Tasks overlap | No | Yes, if demand ≤ capacity | N/A |
| Demand | 1 per task | Integer per task | N/A |
| Production | N/A | N/A | Positive steps |
| Consumption | N/A | N/A | Negative steps |
| Level tracking | No | No | Yes |
| Transition times | Yes | No | No |
| Sequence control | Yes (SequenceVar) | No | No |
Choosing the Right Resource Type
Use Disjunctive (no_overlap) when:
- Resource handles one task at a time
- Task ordering matters
- Transition or setup times exist between tasks
- You need to query task positions in sequence
Use Cumulative when:
- Resource has capacity > 1
- Multiple tasks can execute simultaneously
- Total demand must not exceed capacity
- Tasks have different resource requirements
- You have multiple equivalent resources (e.g., 5 identical workers)
Use Reservoir when:
- Resource is produced and consumed
- Level must stay within bounds
- Production and consumption are decoupled in time
- You need to track resource availability over time
Combining Resource Types
Multiple resource types can be used in the same model:
- Python
- TypeScript
import optalcp as cp
model = cp.Model()
# Tasks
task1 = model.interval_var(length=10, name="task1")
task2 = model.interval_var(length=15, name="task2")
task3 = model.interval_var(length=20, name="task3")
charging = model.interval_var(length=10, name="charging")
# Disjunctive: tasks 1 and 2 need the same machine
model.no_overlap([task1, task2])
# Cumulative: all tasks need workers (capacity = 3)
workers = model.sum([
model.pulse(task1, 2), # task1 needs 2 workers
model.pulse(task2, 1), # task2 needs 1 worker
model.pulse(task3, 2), # task3 needs 2 workers
])
model.enforce(workers <= 3)
# Reservoir: battery level (starts at 50)
battery = model.sum([
model.step_at(cp.IntervalMin, 50), # Initial level
model.step_at_start(task1, -15), # task1 drains battery
model.step_at_start(task2, -20), # task2 drains battery
model.step_at_start(task3, -10), # task3 drains battery
model.step_at_end(charging, 30), # charging adds energy
])
model.enforce(battery >= 10) # Keep 10% reserve
model.enforce(battery <= 100) # Maximum capacity
result = model.solve()
import * as CP from '@scheduleopt/optalcp';
const model = new CP.Model();
// Tasks
const task1 = model.intervalVar({ length: 10, name: "task1" });
const task2 = model.intervalVar({ length: 15, name: "task2" });
const task3 = model.intervalVar({ length: 20, name: "task3" });
const charging = model.intervalVar({ length: 10, name: "charging" });
// Disjunctive: tasks 1 and 2 need the same machine
model.noOverlap([task1, task2]);
// Cumulative: all tasks need workers (capacity = 3)
const workers = model.sum([
model.pulse(task1, 2), // task1 needs 2 workers
model.pulse(task2, 1), // task2 needs 1 worker
model.pulse(task3, 2), // task3 needs 2 workers
]);
model.enforce(workers.le(3));
// Reservoir: battery level (starts at 50)
const battery = model.sum([
model.stepAt(CP.IntervalMin, 50), // Initial level
model.stepAtStart(task1, -15), // task1 drains battery
model.stepAtStart(task2, -20), // task2 drains battery
model.stepAtStart(task3, -10), // task3 drains battery
model.stepAtEnd(charging, 30), // charging adds energy
]);
model.enforce(battery.ge(10)); // Keep 10% reserve
model.enforce(battery.le(100)); // Maximum capacity
const result = await model.solve();
See also
- No Overlap Constraints - Disjunctive resource details
- Cumulative Constraints - Capacity-constrained resources
- Reservoir Constraints - Production and consumption
- Tutorial: No Overlap - Learning path for disjunctive resources
- Tutorial: Cumulative - Learning path for cumulative resources
- Tutorial: Reservoir - Learning path for reservoir resources