Solving Basics
This page covers the fundamentals of solving constraint programming models: calling the solver, interpreting results, and understanding what the solution represents.
Basic Solve
The simplest way to solve a model is to call solve() without parameters:
- Python
- TypeScript
import optalcp as cp
model = cp.Model()
# ... build model ...
result = model.solve()
import * as CP from '@scheduleopt/optalcp';
const model = new CP.Model();
// ... build model ...
const result = await model.solve();
The solve() function returns a SolveResult object containing the solution (if found), proof status, statistics, and search history.
Signature
- Python
- TypeScript
def solve(
params: Parameters | None = None,
warm_start: Solution | None = None
) -> SolveResult
async solve(
params?: Parameters,
warmStart?: Solution
): Promise<SolveResult>
- params: Optional solver parameters (time limit, worker count, etc.).
- warmStart: Optional initial solution. The solver verifies it and uses it as a starting point for the search.
Solve with Parameters
Control solver behavior with parameters:
- Python
- TypeScript
params: cp.Parameters = {
'timeLimit': 60, # 60 seconds
'nbWorkers': 4, # Use 4 parallel workers
'logLevel': 2, # Verbose logging
}
result = model.solve(params)
const params: CP.Parameters = {
timeLimit: 60, // 60 seconds
nbWorkers: 4, // Use 4 parallel workers
logLevel: 2 // Verbose logging
};
const result = await model.solve(params);
Warm Start
Provide an initial solution to guide the search:
- Python
- TypeScript
# Build an 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
# ... set more values ...
result = model.solve(params, warm_start)
// Build an 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
// ... set more values ...
const result = await model.solve(params, warmStart);
See External Solutions for more on building and injecting solutions.
SolveResult Structure
The SolveResult object contains:
- Python
- TypeScript
result.solution # Solution | None - best solution found
result.proof # bool - optimality/infeasibility proved?
result.objective # int | None - objective value
result.objective_bound # int | None - proved bound
result.objective_sense # str | None - 'minimize', 'maximize', None
# Statistics
result.nb_solutions # int - number of solutions found
result.duration # float - solve time in seconds
result.nb_branches # int - search tree branches
result.nb_fails # int - failed search nodes
result.nb_lns_steps # int - LNS iterations
result.nb_restarts # int - search restarts
result.memory_used # int - bytes
result.actual_workers # int - workers used
# Model info
result.nb_int_vars # int - number of IntVars
result.nb_interval_vars # int - number of IntervalVars
result.nb_constraints # int - number of constraints
# History (see Solutions page)
result.objective_history # Sequence[ObjectiveEntry]
result.objective_bound_history # Sequence[ObjectiveBoundEntry]
result.solution_time # float | None - when best solution found
result.bound_time # float | None - when best bound proved
result.solution_valid # bool | None - verification result
# Solver info
result.solver # str - solver version
result.cpu # str - CPU model
result.solution // Solution | undefined - best solution found
result.proof // boolean - optimality/infeasibility proved?
result.objective // number | null | undefined - objective value
result.objectiveBound // number | null | undefined - proved bound
result.objectiveSense // string | undefined - 'minimize', 'maximize', undefined
// Statistics
result.nbSolutions // number - number of solutions found
result.duration // number - solve time in seconds
result.nbBranches // number - search tree branches
result.nbFails // number - failed search nodes
result.nbLNSSteps // number - LNS iterations
result.nbRestarts // number - search restarts
result.memoryUsed // number - bytes
result.actualWorkers // number - workers used
// Model info
result.nbIntVars // number - number of IntVars
result.nbIntervalVars // number - number of IntervalVars
result.nbConstraints // number - number of constraints
// History (see Solutions page)
result.objectiveHistory // ObjectiveEntry[]
result.objectiveBoundHistory // ObjectiveBoundEntry[]
result.solutionTime // number | undefined - when best solution found
result.bestBoundTime // number | undefined - when best bound proved
result.solutionValid // boolean | undefined - verification result
// Solver info
result.solver // string - solver version
result.cpu // string - CPU model
Interpreting Results
Interpret results using the decision tree below:
- Python
- TypeScript
result = model.solve()
if result.solution is not None:
# A solution was found
if result.proof:
print("Optimal solution found!")
# Or: optimal within gap tolerance
else:
print("Feasible solution found (not proven optimal)")
# Stopped due to time/solution limit, Solver.stop(), or Ctrl-C
else:
# No solution found
if result.proof:
print("Problem is infeasible - no solution exists")
else:
print("No solution found yet")
# Stopped due to time/solution limit, Solver.stop(), or Ctrl-C
const result = await model.solve();
if (result.solution !== undefined) {
// A solution was found
if (result.proof) {
console.log("Optimal solution found!");
// Or: optimal within gap tolerance
} else {
console.log("Feasible solution found (not proven optimal)");
// Stopped due to time/solution limit, Solver.stop(), or Ctrl-C
}
} else {
// No solution found
if (result.proof) {
console.log("Problem is infeasible - no solution exists");
} else {
console.log("No solution found yet");
// Stopped due to time/solution limit, Solver.stop(), or Ctrl-C
}
}
Result Interpretation Summary
result.solution | result.proof | Meaning |
|---|---|---|
| Not None | True | Optimal - best possible solution (or within gap tolerance) |
| Not None | False | Feasible - valid solution, may not be optimal |
| None | True | Infeasible - no solution exists |
| None | False | Unknown - no solution found yet |
Optimality Gaps
For optimization problems, you can specify when to stop based on gap tolerances. Default values are absoluteGapTolerance=0 and relativeGapTolerance=0.0001 (0.01%).
- Python
- TypeScript
params: cp.Parameters = {
# Stop when within 5% of optimal
'relativeGapTolerance': 0.05,
# Or: stop when within 100 units
'absoluteGapTolerance': 100,
}
result = model.solve(params)
if result.proof:
# Optimal within specified tolerance
gap = abs(result.objective - result.objective_bound)
print(f"Solution within gap: {gap}")
const params: CP.Parameters = {
// Stop when within 5% of optimal
relativeGapTolerance: 0.05,
// Or: stop when within 100 units
absoluteGapTolerance: 100
};
const result = await model.solve(params);
if (result.proof) {
// Optimal within specified tolerance
const gap = Math.abs(result.objective! - result.objectiveBound!);
console.log(`Solution within gap: ${gap}`);
}
Satisfaction Problems
For satisfaction problems (no objective), the solver automatically stops after finding the first feasible solution:
- Python
- TypeScript
# No objective - just want a feasible schedule
model = cp.Model()
# ... add constraints ...
# Solver automatically stops after first solution
result = model.solve()
if result.solution is not None:
print("Found a valid schedule")
// No objective - just want a feasible schedule
const model = new CP.Model();
// ... add constraints ...
// Solver automatically stops after first solution
const result = await model.solve();
if (result.solution !== undefined) {
console.log("Found a valid schedule");
}
To find multiple solutions, set solutionLimit explicitly:
- Python
- TypeScript
# Find up to 5 different solutions
result = model.solve({'solutionLimit': 5})
print(f"Found {result.nb_solutions} solutions")
// Find up to 5 different solutions
const result = await model.solve({ solutionLimit: 5 });
console.log(`Found ${result.nbSolutions} solutions`);
Controlling Log Output
Two parameters control solver output: logLevel and printLog.
Controlling Verbosity
The logLevel parameter (0-3) controls how much log output the solver generates. Set logLevel=0 to suppress log messages entirely (warnings and errors are still generated).
Similarly, warningLevel (0-3) controls warning verbosity. Set warningLevel=0 to suppress warnings.
- Python
- TypeScript
# Suppress log output entirely (recommended for production)
result = model.solve({'logLevel': 0})
# Suppress both logs and warnings
result = model.solve({'logLevel': 0, 'warningLevel': 0})
// Suppress log output entirely (recommended for production)
const result = await model.solve({ logLevel: 0 });
// Suppress both logs and warnings
const result = await model.solve({ logLevel: 0, warningLevel: 0 });
Redirecting Log Output
The printLog parameter controls where log messages, warnings, and errors are written:
| Value | Node.js | Browser | Python |
|---|---|---|---|
| Default | Console (stdout) | Silent | Console (stdout) |
false/False | Silent | Silent | Silent |
true/True | Console | Console | Console |
| Stream/File | Custom stream | N/A | Custom stream |
- Python
- TypeScript
# Redirect to file
with open('solver.log', 'w') as f:
result = model.solve({'printLog': f})
import * as fs from 'fs';
// Redirect to file (Node.js only)
const logStream = fs.createWriteStream('solver.log');
const result = await model.solve({ printLog: logStream });
logStream.close();
logLevel vs printLog
Choose based on your needs:
-
logLevel=0: The solver doesn't generate log messages at all. Warnings and errors are still generated. Use this for production or benchmarking. -
printLog=false: The solver still generates log, warning, and error messages, but they are not written anywhere by default. Use this when you want to capture messages via callbacks (see Async Solving) without console output.
To suppress output, prefer logLevel=0 over printLog=false. It's more efficient since the solver skips generating log messages entirely.
Complete Example
- Python
- TypeScript
import optalcp as cp
model = cp.Model()
# Create three tasks
task_a = model.interval_var(length=20, name="A")
task_b = model.interval_var(length=15, name="B")
task_c = model.interval_var(length=25, name="C")
# Add constraints
task_a.end_before_start(task_b)
task_b.end_before_start(task_c)
# Minimize makespan
model.minimize(task_c.end())
# Solve with time limit
params: cp.Parameters = {'timeLimit': 30, 'logLevel': 2}
result = model.solve(params)
# Interpret results
if result.solution is not None:
print(f"Makespan: {result.objective}")
print(f"Solutions found: {result.nb_solutions}")
print(f"Solve time: {result.duration:.2f}s")
if result.proof:
print("Proven optimal!")
else:
print(f"Gap: {result.objective - result.objective_bound}")
print("May improve with more time")
# Access solution values
print(f"Task A: {result.solution.get_start(task_a)}-{result.solution.get_end(task_a)}")
print(f"Task B: {result.solution.get_start(task_b)}-{result.solution.get_end(task_b)}")
print(f"Task C: {result.solution.get_start(task_c)}-{result.solution.get_end(task_c)}")
else:
if result.proof:
print("Problem is infeasible")
else:
print("No solution found in time limit")
import * as CP from '@scheduleopt/optalcp';
const model = new CP.Model();
// Create three tasks
const taskA = model.intervalVar({ length: 20, name: "A" });
const taskB = model.intervalVar({ length: 15, name: "B" });
const taskC = model.intervalVar({ length: 25, name: "C" });
// Add constraints
taskA.endBeforeStart(taskB);
taskB.endBeforeStart(taskC);
// Minimize makespan
model.minimize(taskC.end());
// Solve with time limit
const params: CP.Parameters = { timeLimit: 30, logLevel: 2 };
const result = await model.solve(params);
// Interpret results
if (result.solution !== undefined) {
console.log(`Makespan: ${result.objective}`);
console.log(`Solutions found: ${result.nbSolutions}`);
console.log(`Solve time: ${result.duration.toFixed(2)}s`);
if (result.proof) {
console.log("Proven optimal!");
} else {
console.log(`Gap: ${result.objective! - result.objectiveBound!}`);
console.log("May improve with more time");
}
// Access solution values
console.log(`Task A: ${result.solution.getStart(taskA)}-${result.solution.getEnd(taskA)}`);
console.log(`Task B: ${result.solution.getStart(taskB)}-${result.solution.getEnd(taskB)}`);
console.log(`Task C: ${result.solution.getStart(taskC)}-${result.solution.getEnd(taskC)}`);
} else {
if (result.proof) {
console.log("Problem is infeasible");
} else {
console.log("No solution found in time limit");
}
}
In Preview edition, solution values (start/end times, variable values) are masked and reported as absent. The objective value is correct. Contact us for Academic or Full edition for complete solution data.
See Also
- Solutions - Accessing solution values and history
- Async Solving - Progress monitoring with callbacks