Skip to main content

Solutions

The Solution class represents a complete assignment of values to variables. Solutions are returned from the solver and can also be built manually for warm starting or external solution injection.

Accessing Solution Values

Edition Note

In Preview edition, all variables in solutions are reported as absent. The objective value is correct. Contact us for Academic or Full edition for actual solution values.

After solving, access variable values through the Solution object in result.solution. Values are None / null for absent variables.

Interval Variables

result = model.solve()

if result.solution is not None:
solution = result.solution

# Get start and end times
start = solution.get_start(interval_var) # int | None
end = solution.get_end(interval_var) # int | None

# Get both as tuple (convenience)
value = solution.get_value(interval_var) # tuple[int, int] | None

# Check presence
if solution.is_present(interval_var):
print(f"Task runs from {start} to {end}")
else:
print("Task is absent")

Integer and Boolean Variables

# IntVar
int_value = solution.get_value(int_var) # int | None

# BoolVar
bool_value = solution.get_value(bool_var) # bool | None

# Check presence (for optional variables)
if solution.is_present(int_var):
print(f"Value: {int_value}")

Objective Value

objective = solution.get_objective()
# Returns:
# int - the objective value
# None - satisfaction problem (no objective) or objective is absent

Presence Functions

All variables (including non-optional ones) have presence information:

# Check if variable is present in solution
solution.is_present(var) # bool

# Check if variable is absent in solution
solution.is_absent(var) # bool

Non-optional variables are always present. Optional variables may be present or absent depending on the solution.

Iterating Over Model Variables

Iterate over all variables in the model:

# Get all variables
interval_vars = model.get_interval_vars()
int_vars = model.get_int_vars()
bool_vars = model.get_bool_vars()

# Print all interval times
# Note: var.name is None if no name was provided
for var in interval_vars:
if solution.is_present(var):
start = solution.get_start(var)
end = solution.get_end(var)
print(f"{var.name}: [{start}, {end})")
else:
print(f"{var.name}: absent")

Solution History

The SolveResult tracks the objective values of all solutions found during search. Only the best solution is retained in result.solution—for other solutions, only the objective value and timing information is recorded.

result = model.solve()

# Iterate through all solutions found
for entry in result.objective_history:
print(f"Time: {entry.solve_time:.2f}s")
print(f"Objective: {entry.objective}")
print(f"Valid: {entry.valid}")
print()

# Best solution (last entry)
if result.objective_history:
best = result.objective_history[-1]
print(f"Best solution found at {best.solve_time:.2f}s")

ObjectiveEntry

Each entry in objective_history / objectiveHistory represents a solution found during search:

class ObjectiveEntry:
solve_time: float # Time when solution was found (seconds)
objective: int | None # Objective value (None if absent)
valid: bool | None # Verification result (see below)

The valid field is populated when the verifySolution parameter is enabled (not the default). Each solution is verified against the input model by an independent algorithm. The value is true if verification passed, or None / undefined if verification was not enabled. The value is never false—if verification fails, the solver stops with an error.

Objective Bound History

Track improvements to the objective bound (lower bound for minimization, upper bound for maximization):

result = model.solve()

# Iterate through bound improvements
for entry in result.objective_bound_history:
print(f"Time: {entry.solve_time:.2f}s")
print(f"Bound: {entry.value}")
print()

# Best bound (last entry)
if result.objective_bound_history:
best_bound = result.objective_bound_history[-1]
print(f"Best bound: {best_bound.value} at {best_bound.solve_time:.2f}s")

ObjectiveBoundEntry

Each entry in objective_bound_history / objectiveBoundHistory represents an improvement to the proved bound (lower bound for minimization, upper bound for maximization):

class ObjectiveBoundEntry:
solve_time: float # Time when bound was proved (seconds)
value: int # Bound value

Building Solutions

See External Solutions for building solutions manually for warm starting or solution injection.

Complete Example

import optalcp as cp

model = cp.Model()

# Create tasks
cut = model.interval_var(length=30, name="cut")
sand = model.interval_var(length=20, name="sand")
assemble = model.interval_var(length=45, name="assemble")

# Optional quality check
quality_check = model.interval_var(
length=15,
optional=True,
name="quality_check"
)

# Dependencies
cut.end_before_start(sand)
sand.end_before_start(assemble)

# Quality check must be after assembly if present
assemble.end_before_start(quality_check)

# Minimize completion time
model.minimize(model.max([assemble.end(), quality_check.end()]))

# Solve
result = model.solve()

if result.solution is not None:
solution = result.solution

print(f"Objective: {solution.get_objective()}")
print(f"Solutions found: {result.nb_solutions}")
print()

# Print all interval variables
for var in model.get_interval_vars():
if solution.is_present(var):
start = solution.get_start(var)
end = solution.get_end(var)
print(f"{var.name}: {start} -> {end} (length {end - start})")
else:
print(f"{var.name}: absent")

# Show solution history
print(f"\nSolution history:")
for i, entry in enumerate(result.objective_history):
print(f" Solution {i+1}: objective={entry.objective} at {entry.solve_time:.2f}s")

See Also