Step Functions
IntStepFunction is a piecewise-constant function defined at modeling time. Step functions provide constant data that the solver uses to model calendars, availability windows, time-dependent costs, energy pricing, efficiency rates, and other time-varying quantities. The function itself doesn't change during solving—it's static input data.
Creating Step Functions
Create a step function using model.step_function():
- Python
- TypeScript
import optalcp as cp
model = cp.Model()
func = model.step_function([
# Implicitly: value is 0 before time 0
(0, 10), # From time 0: value is 10
(100, 20), # From time 100: value is 20
(200, 15), # From time 200 onwards: value is 15
])
import * as CP from '@scheduleopt/optalcp';
const model = new CP.Model();
const func = model.stepFunction([
// Implicitly: value is 0 before time 0
[0, 10], // From time 0: value is 10
[100, 20], // From time 100: value is 20
[200, 15], // From time 200 onwards: value is 15
]);
Parameters
- values: Iterable of
(time, value)pairs defining the function. Must be sorted by time in ascending order.
Semantics
A step function defined by points :
The function value at any point is the value from the most recent step at or before that point. Before the first step, the value is 0.
Evaluating Step Functions
Point Evaluation
Evaluate the function at a specific point in time:
- Python
- TypeScript
# Cost varies by time of day
cost_func = model.step_function([
(0, 10), # 0h-8h: low cost ($10)
(8, 20), # 8h-17h: high cost ($20)
(17, 10), # 17h onwards: low cost ($10)
])
task = model.interval_var(length=5, name="task")
# Evaluate cost at task start
start_cost = model.eval(cost_func, task.start())
model.minimize(start_cost)
// Cost varies by time of day
const costFunc = model.stepFunction([
[0, 10], // 0h-8h: low cost ($10)
[8, 20], // 8h-17h: high cost ($20)
[17, 10], // 17h onwards: low cost ($10)
]);
const task = model.intervalVar({ length: 5, name: "task" });
// Evaluate cost at task start
const startCost = model.eval(costFunc, task.start());
model.minimize(startCost);
Integral Over Interval
Compute the sum of function values at each integer point in the interval:
The interval is half-open: includes start, excludes end. If the interval has zero length (), the result is 0. If the interval is absent, the result is absent.
Requirement: The step function must be non-negative.
- Python
- TypeScript
# Energy rate per time unit
energy_rate = model.step_function([
(0, 5), # 0h-8h: off-peak ($5/hour)
(8, 15), # 8h-18h: peak ($15/hour)
(18, 5), # 18h onwards: off-peak ($5/hour)
])
task = model.interval_var(length=10, name="task")
# Total energy cost = sum of rate over task duration
total_cost = model.integral(energy_rate, task)
model.minimize(total_cost)
// Energy rate per time unit
const energyRate = model.stepFunction([
[0, 5], // 0h-8h: off-peak ($5/hour)
[8, 15], // 8h-18h: peak ($15/hour)
[18, 5], // 18h onwards: off-peak ($5/hour)
]);
const task = model.intervalVar({ length: 10, name: "task" });
// Total energy cost = sum of rate over task duration
const totalCost = model.integral(energyRate, task);
model.minimize(totalCost);
Forbid Constraints
Step functions can forbid task placement in time windows where the function value is zero.
Forbid Start / End
Require the function to be non-zero at task start or end:
- Python
- TypeScript
# Availability: 1 = available, 0 = unavailable
availability = model.step_function([
(0, 1), # 0h-8h: available
(8, 0), # 8h-12h: break
(12, 1), # 12h onwards: available
])
task = model.interval_var(length=5, name="task")
# Task cannot start when availability is 0
task.forbid_start(availability)
# Equivalent to:
model.enforce(availability.eval(task.start()) != 0)
# Task cannot end when availability is 0
task.forbid_end(availability)
# Equivalent to:
model.enforce(availability.eval(task.end()) != 0)
// Availability: 1 = available, 0 = unavailable
const availability = model.stepFunction([
[0, 1], // 0h-8h: available
[8, 0], // 8h-12h: break
[12, 1], // 12h onwards: available
]);
const task = model.intervalVar({ length: 5, name: "task" });
// Task cannot start when availability is 0
task.forbidStart(availability);
// Equivalent to:
model.enforce(availability.eval(task.start()).ne(0))
// Task cannot end when availability is 0
task.forbidEnd(availability);
// Equivalent to:
model.enforce(availability.eval(task.end()).ne(0))
Forbid Extent
Require the function to be non-zero throughout the entire task:
- Python
- TypeScript
# Machine availability calendar
availability = model.step_function([
(0, 1), # 0h-8h: available
(8, 0), # 8h-12h: maintenance
(12, 1), # 12h-20h: available
(20, 0), # 20h onwards: closed
])
task = model.interval_var(length=5, name="task")
# Task cannot overlap any period where availability is 0
task.forbid_extent(availability)
// Machine availability calendar
const availability = model.stepFunction([
[0, 1], // 0h-8h: available
[8, 0], // 8h-12h: maintenance
[12, 1], // 12h-20h: available
[20, 0], // 20h onwards: closed
]);
const task = model.intervalVar({ length: 5, name: "task" });
// Task cannot overlap any period where availability is 0
task.forbidExtent(availability);
Use Cases
Time-Dependent Costs
Model costs that vary throughout the day:
- Python
- TypeScript
electricity_cost = model.step_function([
(0, 5), # 0h-6h: night ($5/hour)
(6, 8), # 6h-9h: morning ($8/hour)
(9, 15), # 9h-17h: peak ($15/hour)
(17, 8), # 17h-22h: evening ($8/hour)
(22, 5), # 22h onwards: night ($5/hour)
])
tasks = [model.interval_var(length=10) for _ in range(5)]
# Minimize total electricity cost
total_cost = model.sum([
model.integral(electricity_cost, task) for task in tasks
])
model.minimize(total_cost)
const electricityCost = model.stepFunction([
[0, 5], // 0h-6h: night ($5/hour)
[6, 8], // 6h-9h: morning ($8/hour)
[9, 15], // 9h-17h: peak ($15/hour)
[17, 8], // 17h-22h: evening ($8/hour)
[22, 5], // 22h onwards: night ($5/hour)
]);
const tasks = Array.from({ length: 5 }, () => model.intervalVar({ length: 10 }));
// Minimize total electricity cost
const totalCost = model.sum(
tasks.map(task => model.integral(electricityCost, task))
);
model.minimize(totalCost);
Availability Windows
Model resource availability over time:
- Python
- TypeScript
# Machine available only during working hours
working_hours = model.step_function([
(0, 0), # 0h-8h: closed
(8, 1), # 8h-17h: open
(17, 0), # 17h onwards: closed
])
tasks = [model.interval_var(length=5) for _ in range(10)]
# All tasks must execute during working hours
for task in tasks:
task.forbid_extent(working_hours)
model.minimize(model.max([t.end() for t in tasks]))
// Machine available only during working hours
const workingHours = model.stepFunction([
[0, 0], // 0h-8h: closed
[8, 1], // 8h-17h: open
[17, 0], // 17h onwards: closed
]);
const tasks = Array.from({ length: 10 }, () => model.intervalVar({ length: 5 }));
// All tasks must execute during working hours
tasks.forEach(task => task.forbidExtent(workingHours));
model.minimize(model.max(tasks.map(t => t.end())));
Minimum Collected Value
Require a task to accumulate a minimum total from a step function:
- Python
- TypeScript
# Sunlight intensity throughout the day
sunlight = model.step_function([
(0, 0), # 0h-6h: dark
(6, 3), # 6h-9h: morning
(9, 10), # 9h-15h: peak sunlight
(15, 5), # 15h-18h: afternoon
(18, 0), # 18h onwards: dark
])
# Solar charging task with variable length
charging = model.interval_var(length=(10, 50), name="charging")
# Must collect at least 100 units of sunlight
model.enforce(model.integral(sunlight, charging) >= 100)
// Sunlight intensity throughout the day
const sunlight = model.stepFunction([
[0, 0], // 0h-6h: dark
[6, 3], // 6h-9h: morning
[9, 10], // 9h-15h: peak sunlight
[15, 5], // 15h-18h: afternoon
[18, 0], // 18h onwards: dark
]);
// Solar charging task with variable length
const charging = model.intervalVar({ length: [10, 50], name: "charging" });
// Must collect at least 100 units of sunlight
model.enforce(model.integral(sunlight, charging).ge(100));
Maintenance Windows
Forbid task execution during scheduled maintenance:
- Python
- TypeScript
# Machine status: 1 = available, 0 = maintenance
machine_status = model.step_function([
(0, 1), # 0-100: available
(100, 0), # 100-120: maintenance
(120, 1), # 120-200: available
(200, 0), # 200-220: maintenance
(220, 1), # 220 onwards: available
])
tasks = [model.interval_var(length=15) for _ in range(10)]
# Tasks cannot run during maintenance
for task in tasks:
task.forbid_extent(machine_status)
model.no_overlap(tasks)
// Machine status: 1 = available, 0 = maintenance
const machineStatus = model.stepFunction([
[0, 1], // 0-100: available
[100, 0], // 100-120: maintenance
[120, 1], // 120-200: available
[200, 0], // 200-220: maintenance
[220, 1], // 220 onwards: available
]);
const tasks = Array.from({ length: 10 }, () => model.intervalVar({ length: 15 }));
// Tasks cannot run during maintenance
tasks.forEach(task => task.forbidExtent(machineStatus));
model.noOverlap(tasks);
Complete Example
- Python
- TypeScript
import optalcp as cp
model = cp.Model()
# Energy pricing (24-hour cycle)
energy_price = model.step_function([
(0, 5), # 0h-6h: low ($5/hour)
(6, 10), # 6h-9h: medium ($10/hour)
(9, 20), # 9h-17h: peak ($20/hour)
(17, 10), # 17h-22h: medium ($10/hour)
(22, 5), # 22h onwards: low ($5/hour)
])
# Machine availability
machine_hours = model.step_function([
(0, 0), # 0h-6h: closed
(6, 1), # 6h-22h: open
(22, 0), # 22h onwards: closed
])
tasks = [
model.interval_var(length=8, name=f"task_{i}")
for i in range(5)
]
# Tasks must run during machine hours
for task in tasks:
task.forbid_extent(machine_hours)
# Sequential execution
for i in range(len(tasks) - 1):
tasks[i].end_before_start(tasks[i + 1])
# Minimize total energy cost
total_cost = model.sum([
model.integral(energy_price, task) for task in tasks
])
model.minimize(total_cost)
result = model.solve()
if result.solution:
print(f"Total energy cost: ${result.objective}")
for task in tasks:
start = result.solution.get_start(task)
end = result.solution.get_end(task)
print(f"{task.name}: {start}-{end}")
import * as CP from '@scheduleopt/optalcp';
const model = new CP.Model();
// Energy pricing (24-hour cycle)
const energyPrice = model.stepFunction([
[0, 5], // 0h-6h: low ($5/hour)
[6, 10], // 6h-9h: medium ($10/hour)
[9, 20], // 9h-17h: peak ($20/hour)
[17, 10], // 17h-22h: medium ($10/hour)
[22, 5], // 22h onwards: low ($5/hour)
]);
// Machine availability
const machineHours = model.stepFunction([
[0, 0], // 0h-6h: closed
[6, 1], // 6h-22h: open
[22, 0], // 22h onwards: closed
]);
const tasks = Array.from({ length: 5 }, (_, i) =>
model.intervalVar({ length: 8, name: `task_${i}` })
);
// Tasks must run during machine hours
tasks.forEach(task => task.forbidExtent(machineHours));
// Sequential execution
for (let i = 0; i < tasks.length - 1; i++) {
tasks[i].endBeforeStart(tasks[i + 1]);
}
// Minimize total energy cost
const totalCost = model.sum(
tasks.map(task => model.integral(energyPrice, task))
);
model.minimize(totalCost);
const result = await model.solve();
if (result.solution) {
console.log(`Total energy cost: $${result.objective}`);
tasks.forEach(task => {
const start = result.solution.getStart(task);
const end = result.solution.getEnd(task);
console.log(`${task.name}: ${start}-${end}`);
});
}
Edge Cases
Value Before First Step
Before the first step, the function value is 0:
- Python
- TypeScript
func = model.step_function([
# Implicitly: value is 0 before time 10
(10, 5), # From time 10: value is 5
(20, 10), # From time 20 onwards: value is 10
])
const func = model.stepFunction([
// Implicitly: value is 0 before time 10
[10, 5], // From time 10: value is 5
[20, 10], // From time 20 onwards: value is 10
]);
Tip: For forbid start, forbid end, and forbid extent, the constraint forbids times where the function is zero. If you don't define a step at time 0, tasks cannot start/end/run before the first defined step.
Empty Steps
An empty step function is valid—the function is 0 everywhere:
- Python
- TypeScript
func = model.step_function([]) # func(x) = 0 for all x
const func = model.stepFunction([]); // func(x) = 0 for all x
See Also
- Intervals - IntervalVar forbid functions
- Expressions - Using evaluated step functions in expressions
- Objectives - Optimizing costs with step functions
- Resources/Cumulative - Time-varying resource capacity
- Tutorial - Step-by-step learning path