External Solutions
OptalCP allows you to inject external solutions into the solver, either as a warm start before solving or dynamically during search. This enables integration with custom heuristics, previous solutions, or external optimization tools.
Building Solutions
Before providing a solution to the solver, you need to build it. The constructor creates a solution with all variables absent—you then set values for the variables you want to be present:
- Python
- TypeScript
solution = cp.Solution()
# Set interval start and end (both required)
solution.set_value(task1, 0, 10) # start=0, end=10
solution.set_value(task2, 30, 45) # start=30, end=45
# Set integer variable
solution.set_value(int_var, 42)
# Set boolean variable
solution.set_value(bool_var, True)
# Mark variable as absent
solution.set_absent(optional_task)
# Set objective value
solution.set_objective(150)
const solution = new CP.Solution();
// Set interval start and end (both required)
solution.setValue(task1, 0, 10); // start=0, end=10
solution.setValue(task2, 30, 45); // start=30, end=45
// Set integer variable
solution.setValue(intVar, 42);
// Set boolean variable
solution.setValue(boolVar, true);
// Mark variable as absent
solution.setAbsent(optionalTask);
// Set objective value
solution.setObjective(150);
All variables must be set, including the objective value. Partial solutions are not supported.
The solver automatically validates all external solutions. It checks constraint satisfaction and rejects invalid solutions with a warning.
Warm Start
Provide an initial solution when calling solve():
- Python
- TypeScript
import optalcp as cp
model = cp.Model()
# ... build model ...
# Build initial solution
warm_start = cp.Solution()
warm_start.set_value(task1, 0, 20) # start=0, end=20
warm_start.set_value(task2, 30, 50) # start=30, end=50
warm_start.set_value(task3, 60, 85) # start=60, end=85
# Solve with warm start
result = model.solve(params, warm_start)
import * as CP from '@scheduleopt/optalcp';
const model = new CP.Model();
// ... build model ...
// Build initial solution
const warmStart = new CP.Solution();
warmStart.setValue(task1, 0, 20); // start=0, end=20
warmStart.setValue(task2, 30, 50); // start=30, end=50
warmStart.setValue(task3, 60, 85); // start=60, end=85
// Solve with warm start
const result = await model.solve(params, warmStart);
The solver uses the warm start to:
- Verify feasibility
- Establish an initial bound
- Guide search toward similar solutions
Dynamic Solution Injection
Inject solutions during search using the Solver class:
- Python
- TypeScript
import asyncio
import optalcp as cp
solver = cp.Solver()
# Start solving in background
solve_task = asyncio.create_task(solver.solve(model, params))
# Wait a bit for search to start
await asyncio.sleep(1)
# Build and inject a solution
solution = cp.Solution()
solution.set_value(task1, 5, 25) # start=5, end=25
solution.set_value(task2, 35, 55) # start=35, end=55
solution.set_value(task3, 70, 95) # start=70, end=95
await solver.send_solution(solution)
# Wait for final result
result = await solve_task
import * as CP from '@scheduleopt/optalcp';
const solver = new CP.Solver();
// Start solving in background
const solvePromise = solver.solve(model, params);
// Wait a bit for search to start
await new Promise(resolve => setTimeout(resolve, 1000));
// Build and inject a solution
const solution = new CP.Solution();
solution.setValue(task1, 5, 25); // start=5, end=25
solution.setValue(task2, 35, 55); // start=35, end=55
solution.setValue(task3, 70, 95); // start=70, end=95
await solver.sendSolution(solution);
// Wait for final result
const result = await solvePromise;
The solver validates and integrates injected solutions during search.
For a complete working example, see demo-external-solutions.
Use Cases
1. Reusing Previous Solutions
Save solutions from previous runs and reuse them:
- Python
- TypeScript
# First solve
result1 = model.solve()
if result1.solution is not None:
# Save for later
previous_solution = result1.solution
# Later: similar model
model2 = cp.Model()
# ... build similar model ...
# Reuse previous solution as warm start
result2 = model2.solve(params, previous_solution)
// First solve
const result1 = await model.solve();
if (result1.solution !== undefined) {
// Save for later
const previousSolution = result1.solution;
}
// Later: similar model
const model2 = new CP.Model();
// ... build similar model ...
// Reuse previous solution as warm start
const result2 = await model2.solve(params, previousSolution);
2. Custom Heuristics
Integrate domain-specific heuristics:
- Python
- TypeScript
import optalcp as cp
def greedy_heuristic(model, tasks):
"""Simple greedy schedule: tasks in order, earliest start."""
solution = cp.Solution()
current_time = 0
for task in tasks:
length = task.length_max
solution.set_value(task, current_time, current_time + length)
current_time += length
return solution
# Build model
model = cp.Model()
tasks = [model.interval_var(length=l, name=f"t{i}") for i, l in enumerate([10, 20, 15])]
# ... add constraints ...
# Generate heuristic solution
heuristic_solution = greedy_heuristic(model, tasks)
# Use as warm start
result = model.solve(params, heuristic_solution)
import * as CP from '@scheduleopt/optalcp';
function greedyHeuristic(model: CP.Model, tasks: CP.IntervalVar[]): CP.Solution {
// Simple greedy schedule: tasks in order, earliest start
const solution = new CP.Solution();
let currentTime = 0;
for (const task of tasks) {
const length = task.lengthMax!;
solution.setValue(task, currentTime, currentTime + length);
currentTime += length;
}
return solution;
}
// Build model
const model = new CP.Model();
const tasks = [10, 20, 15].map((l, i) => model.intervalVar({ length: l, name: `t${i}` }));
// ... add constraints ...
// Generate heuristic solution
const heuristicSolution = greedyHeuristic(model, tasks);
// Use as warm start
const result = await model.solve(params, heuristicSolution);
When running heuristics concurrently with the solver, do not block the event loop—this would block communication with the solver process. For CPU-intensive heuristics, use subprocesses or threads.
3. Parallel Heuristic Search
Run multiple heuristics in parallel and inject solutions:
- Python
- TypeScript
import asyncio
import optalcp as cp
async def run_heuristic(solver, model, heuristic_fn):
"""Run heuristic and inject solution."""
await asyncio.sleep(0.5) # Simulate heuristic computation
solution = heuristic_fn(model)
await solver.send_solution(solution)
async def solve_with_heuristics(model, params, heuristics):
solver = cp.Solver()
# Start solver
solve_task = asyncio.create_task(solver.solve(model, params))
# Run heuristics in parallel
heuristic_tasks = [
asyncio.create_task(run_heuristic(solver, model, h))
for h in heuristics
]
# Wait for all heuristics
await asyncio.gather(*heuristic_tasks)
# Wait for solve to complete
return await solve_task
# Define heuristics
heuristics = [greedy_heuristic, random_heuristic, priority_heuristic]
# Solve with parallel heuristics
result = await solve_with_heuristics(model, params, heuristics)
import * as CP from '@scheduleopt/optalcp';
async function runHeuristic(
solver: CP.Solver,
model: CP.Model,
heuristicFn: (model: CP.Model) => CP.Solution
) {
// Simulate heuristic computation
await new Promise(resolve => setTimeout(resolve, 500));
const solution = heuristicFn(model);
await solver.sendSolution(solution);
}
async function solveWithHeuristics(
model: CP.Model,
params: CP.Parameters,
heuristics: Array<(model: CP.Model) => CP.Solution>
): Promise<CP.SolveResult> {
const solver = new CP.Solver();
// Start solver
const solvePromise = solver.solve(model, params);
// Run heuristics in parallel
const heuristicPromises = heuristics.map(h =>
runHeuristic(solver, model, h)
);
// Wait for all heuristics
await Promise.all(heuristicPromises);
// Wait for solve to complete
return await solvePromise;
}
// Define heuristics
const heuristics = [greedyHeuristic, randomHeuristic, priorityHeuristic];
// Solve with parallel heuristics
const result = await solveWithHeuristics(model, params, heuristics);
4. Iterative Improvement
Solve in stages with progressively tighter constraints:
- Python
- TypeScript
import optalcp as cp
# Stage 1: Quick solve for feasibility
model1 = cp.Model()
# ... build with relaxed constraints ...
params: cp.Parameters = {'timeLimit': 10, 'solutionLimit': 1}
result1 = model1.solve(params)
if result1.solution is not None:
# Stage 2: Add more constraints, use previous solution as warm start
model2 = cp.Model()
# ... build with tighter constraints ...
params = {'timeLimit': 60}
result2 = model2.solve(params, result1.solution)
print(f"Stage 1 objective: {result1.objective}")
print(f"Stage 2 objective: {result2.objective}")
import * as CP from '@scheduleopt/optalcp';
// Stage 1: Quick solve for feasibility
const model1 = new CP.Model();
// ... build with relaxed constraints ...
let params: CP.Parameters = { timeLimit: 10, solutionLimit: 1 };
const result1 = await model1.solve(params);
if (result1.solution !== undefined) {
// Stage 2: Add more constraints, use previous solution as warm start
const model2 = new CP.Model();
// ... build with tighter constraints ...
params = { timeLimit: 60 };
const result2 = await model2.solve(params, result1.solution);
console.log(`Stage 1 objective: ${result1.objective}`);
console.log(`Stage 2 objective: ${result2.objective}`);
}
Complete Example
- Python
- TypeScript
import asyncio
import optalcp as cp
# Build model
model = cp.Model()
tasks = [model.interval_var(length=l, name=f"task{i}") for i, l in enumerate([20, 30, 25])]
# Add precedence constraints
tasks[0].end_before_start(tasks[1])
tasks[1].end_before_start(tasks[2])
# Minimize makespan
model.minimize(tasks[2].end())
# Create heuristic solution (tasks have lengths 20, 30, 25)
heuristic = cp.Solution()
heuristic.set_value(tasks[0], 0, 20) # length=20
heuristic.set_value(tasks[1], 20, 50) # length=30
heuristic.set_value(tasks[2], 50, 75) # length=25
# Solve with warm start
print("Solving with warm start...")
params: cp.Parameters = {'timeLimit': 30}
result = model.solve(params, heuristic)
print(f"With warm start: {result.objective}, time: {result.duration:.2f}s")
# Compare: solve without warm start
print("\nSolving without warm start...")
result_no_warm = model.solve(params)
print(f"Without warm start: {result_no_warm.objective}, time: {result_no_warm.duration:.2f}s")
print(f"Speedup: {result_no_warm.duration / result.duration:.2f}x")
import * as CP from '@scheduleopt/optalcp';
// Build model
const model = new CP.Model();
const tasks = [20, 30, 25].map((l, i) => model.intervalVar({ length: l, name: `task${i}` }));
// Add precedence constraints
tasks[0].endBeforeStart(tasks[1]);
tasks[1].endBeforeStart(tasks[2]);
// Minimize makespan
model.minimize(tasks[2].end());
// Create heuristic solution (tasks have lengths 20, 30, 25)
const heuristic = new CP.Solution();
heuristic.setValue(tasks[0], 0, 20); // length=20
heuristic.setValue(tasks[1], 20, 50); // length=30
heuristic.setValue(tasks[2], 50, 75); // length=25
// Solve with warm start
console.log("Solving with warm start...");
const params: CP.Parameters = { timeLimit: 30 };
const result = await model.solve(params, heuristic);
console.log(`With warm start: ${result.objective}, time: ${result.duration.toFixed(2)}s`);
// Compare: solve without warm start
console.log("\nSolving without warm start...");
const resultNoWarm = await model.solve(params);
console.log(`Without warm start: ${resultNoWarm.objective}, time: ${resultNoWarm.duration.toFixed(2)}s`);
console.log(`Speedup: ${(resultNoWarm.duration / result.duration).toFixed(2)}x`);
See Also
- Solutions - Building and accessing solution objects
- Async Solving - Using
Solver.send_solution()during search - Model Export - Serializing solutions with models
- Solving Basics - Warm start parameter in
solve()