From 15e43e6718e4d178806ac0bdf9786deb91b53a05 Mon Sep 17 00:00:00 2001 From: Ishika Roy Date: Mon, 9 Mar 2026 18:55:16 -0700 Subject: [PATCH 1/4] update doc for usage of incumbent utility function --- python/cuopt/cuopt/linear_programming/problem.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/python/cuopt/cuopt/linear_programming/problem.py b/python/cuopt/cuopt/linear_programming/problem.py index 80cb83ee6f..e62504686d 100644 --- a/python/cuopt/cuopt/linear_programming/problem.py +++ b/python/cuopt/cuopt/linear_programming/problem.py @@ -1703,7 +1703,16 @@ def updateObjective(self, coeffs=[], constant=None, sense=None): def getIncumbentValues(self, solution, vars): """ - Extract incumbent values of the vars from a problem solution. + This is a utility function that can be used for extracting incumbent values + of the given variables during a Solve using the incumbent callback. + Please check docs for more details and examples of incumbent callbacks. + + Parameters + ---------- + solution : List[float] + Array-like structure containing incumbent values. + vars : List[:py:class:`Variable`] + List of variables to extract corresponding incumbent values. """ values = [] for var in vars: From 38f0a8d94bc8d57148c8f13545d632214ccfad57 Mon Sep 17 00:00:00 2001 From: Ramakrishna Prabhu Date: Thu, 19 Mar 2026 11:02:16 -0500 Subject: [PATCH 2/4] Update incumbent examples --- .../examples/incumbent_solutions_example.py | 54 +++++++++---------- .../lp-qp-milp/lp-qp-milp-examples.rst | 11 ++-- .../assets/milp_basic/incumbent_callback.py | 16 +++--- 3 files changed, 37 insertions(+), 44 deletions(-) diff --git a/docs/cuopt/source/cuopt-python/lp-qp-milp/examples/incumbent_solutions_example.py b/docs/cuopt/source/cuopt-python/lp-qp-milp/examples/incumbent_solutions_example.py index 39460436e3..11a6d367aa 100644 --- a/docs/cuopt/source/cuopt-python/lp-qp-milp/examples/incumbent_solutions_example.py +++ b/docs/cuopt/source/cuopt-python/lp-qp-milp/examples/incumbent_solutions_example.py @@ -6,6 +6,8 @@ This example demonstrates: - Using callbacks to receive intermediate solutions during MIP solving +- Using Problem.getIncumbentValues() to extract variable values from + incumbent solutions - Tracking solution progress as the solver improves the solution - Accessing incumbent (best so far) solutions before final optimum - Custom callback class implementation @@ -26,12 +28,11 @@ x, y are integers Expected Output: - Incumbent 1: [ 0. 58.], cost: 174.00 - Incumbent 2: [36. 41.], cost: 303.00 + Incumbent 1: x=36.0, y=41.0, cost: 303.00 === Final Results === Problem status: Optimal - Solve time: 0.16 seconds + Solve time: 0.27 seconds Final solution: x=36.0, y=41.0 Final objective value: 303.00 """ @@ -42,59 +43,55 @@ from cuopt.linear_programming.internals import GetSolutionCallback -# Create a callback class to receive incumbent solutions class IncumbentCallback(GetSolutionCallback): - """Callback to receive and track incumbent solutions during solving.""" + """Callback to receive and track incumbent solutions during solving. - def __init__(self, user_data): + Uses Problem.getIncumbentValues() to extract variable values from the + raw incumbent solution array. + """ + + def __init__(self, problem, variables, user_data): super().__init__() + self.problem = problem + self.variables = variables self.solutions = [] self.n_callbacks = 0 self.user_data = user_data def get_solution(self, solution, solution_cost, solution_bound, user_data): + """Called whenever the solver finds a new incumbent solution.""" assert user_data is self.user_data - """ - Called whenever the solver finds a new incumbent solution. - - Parameters - ---------- - solution : array-like - The variable values of the incumbent solution - solution_cost : array-like - The objective value of the incumbent solution - solution_bound : array-like - The current best bound in user objective space - """ self.n_callbacks += 1 - # Store the incumbent solution + # Use getIncumbentValues to extract values for specific variables + values = self.problem.getIncumbentValues( + solution, self.variables + ) + incumbent = { - "solution": solution.tolist(), + "values": values, "cost": float(solution_cost[0]), "bound": float(solution_bound[0]), "iteration": self.n_callbacks, - "user_data": user_data, } self.solutions.append(incumbent) print( - f"Incumbent {self.n_callbacks}: {incumbent['solution']}, " + f"Incumbent {self.n_callbacks}: " + f"x={values[0]}, y={values[1]}, " f"cost: {incumbent['cost']:.2f}" ) def main(): """Run the incumbent solutions example.""" - # Create a more complex MIP problem that will generate multiple incumbents problem = Problem("Incumbent Example") # Add integer variables x = problem.addVariable(vtype=INTEGER) y = problem.addVariable(vtype=INTEGER) - # Add constraints to create a problem that will generate multiple - # incumbents + # Add constraints problem.addConstraint(2 * x + 4 * y >= 230) problem.addConstraint(3 * x + 2 * y <= 190) @@ -103,11 +100,11 @@ def main(): # Configure solver settings with callback settings = SolverSettings() - # Set the incumbent callback user_data = {"source": "incumbent_solutions_example"} - incumbent_callback = IncumbentCallback(user_data) + incumbent_callback = IncumbentCallback( + problem, [x, y], user_data + ) settings.set_mip_callback(incumbent_callback, user_data) - # Allow enough time to find multiple incumbents settings.set_parameter(CUOPT_TIME_LIMIT, 30) # Solve the problem @@ -120,7 +117,6 @@ def main(): print(f"Final solution: x={x.getValue()}, y={y.getValue()}") print(f"Final objective value: {problem.ObjValue:.2f}") - # Display all incumbents found print( f"\nTotal incumbent solutions found: " f"{len(incumbent_callback.solutions)}" diff --git a/docs/cuopt/source/cuopt-python/lp-qp-milp/lp-qp-milp-examples.rst b/docs/cuopt/source/cuopt-python/lp-qp-milp/lp-qp-milp-examples.rst index a3412d1a82..920f594a0b 100644 --- a/docs/cuopt/source/cuopt-python/lp-qp-milp/lp-qp-milp-examples.rst +++ b/docs/cuopt/source/cuopt-python/lp-qp-milp/lp-qp-milp-examples.rst @@ -166,17 +166,12 @@ The response is as follows: .. code-block:: text - Optimal solution found. - Incumbent 1: [ 0. 58.], cost: 174.00 - Incumbent 2: [36. 41.], cost: 303.00 - Generated fast solution in 0.158467 seconds with objective 303.000000 - Consuming B&B solutions, solution queue size 2 - Solution objective: 303.000000 , relative_mip_gap 0.000000 solution_bound 303.000000 presolve_time 0.043211 total_solve_time 0.160270 max constraint violation 0.000000 max int violation 0.000000 max var bounds violation 0.000000 nodes 4 simplex_iterations 3 + Incumbent 1: x=36.0, y=41.0, cost: 303.00 === Final Results === Problem status: Optimal - Solve time: 0.16 seconds - Final solution: x=36.0, y=40.99999999999999 + Solve time: 0.27 seconds + Final solution: x=36.0, y=41.0 Final objective value: 303.00 Working with PDLP Warmstart Data diff --git a/skills/cuopt-lp-milp-api-python/assets/milp_basic/incumbent_callback.py b/skills/cuopt-lp-milp-api-python/assets/milp_basic/incumbent_callback.py index 49e533291c..e36d0b22b6 100644 --- a/skills/cuopt-lp-milp-api-python/assets/milp_basic/incumbent_callback.py +++ b/skills/cuopt-lp-milp-api-python/assets/milp_basic/incumbent_callback.py @@ -13,20 +13,21 @@ class IncumbentCallback(GetSolutionCallback): - def __init__(self, user_data): + def __init__(self, problem, variables, user_data): super().__init__() + self.problem = problem + self.variables = variables self.n_callbacks = 0 self.user_data = user_data def get_solution(self, solution, solution_cost, solution_bound, user_data): self.n_callbacks += 1 - sol = ( - solution.tolist() - if hasattr(solution, "tolist") - else list(solution) + values = self.problem.getIncumbentValues( + solution, self.variables ) cost = float(solution_cost[0]) - print(f"Incumbent {self.n_callbacks}: {sol}, cost: {cost:.2f}") + vals_str = ", ".join(f"{float(v)}" for v in values) + print(f"Incumbent {self.n_callbacks}: [{vals_str}], cost: {cost:.2f}") def main(): @@ -39,7 +40,8 @@ def main(): user_data = {"source": "incumbent_callback"} settings = SolverSettings() - settings.set_mip_callback(IncumbentCallback(user_data), user_data) + callback = IncumbentCallback(problem, [x, y], user_data) + settings.set_mip_callback(callback, user_data) settings.set_parameter(CUOPT_TIME_LIMIT, 30) problem.solve(settings) From 955d25303f6a5e3217bc8b0caabb9a8c14201acc Mon Sep 17 00:00:00 2001 From: Ishika Roy Date: Fri, 20 Mar 2026 10:26:01 -0700 Subject: [PATCH 3/4] update example --- .../examples/incumbent_solutions_example.py | 19 ++++++++++--------- .../lp-qp-milp/lp-qp-milp-examples.rst | 10 +++++++--- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/docs/cuopt/source/cuopt-python/lp-qp-milp/examples/incumbent_solutions_example.py b/docs/cuopt/source/cuopt-python/lp-qp-milp/examples/incumbent_solutions_example.py index 11a6d367aa..ce84a7f453 100644 --- a/docs/cuopt/source/cuopt-python/lp-qp-milp/examples/incumbent_solutions_example.py +++ b/docs/cuopt/source/cuopt-python/lp-qp-milp/examples/incumbent_solutions_example.py @@ -76,11 +76,10 @@ def get_solution(self, solution, solution_cost, solution_bound, user_data): } self.solutions.append(incumbent) - print( - f"Incumbent {self.n_callbacks}: " - f"x={values[0]}, y={values[1]}, " - f"cost: {incumbent['cost']:.2f}" - ) + print(f"Incumbent {self.n_callbacks}:", end=' ') + for i, var in enumerate(self.variables): + print(f"{var.VariableName}={values[i]}", end=' ') + print(f"cost: {incumbent['cost']:.2f}") def main(): @@ -88,8 +87,8 @@ def main(): problem = Problem("Incumbent Example") # Add integer variables - x = problem.addVariable(vtype=INTEGER) - y = problem.addVariable(vtype=INTEGER) + x = problem.addVariable(vtype=INTEGER, name='x') + y = problem.addVariable(vtype=INTEGER, name='y') # Add constraints problem.addConstraint(2 * x + 4 * y >= 230) @@ -114,8 +113,10 @@ def main(): print("\n=== Final Results ===") print(f"Problem status: {problem.Status.name}") print(f"Solve time: {problem.SolveTime:.2f} seconds") - print(f"Final solution: x={x.getValue()}, y={y.getValue()}") - print(f"Final objective value: {problem.ObjValue:.2f}") + print(f"Final solution: ", end=' ') + for i, var in enumerate(problem.getVariables()): + print(f"{var.VariableName}={var.getValue()} ", end=' ') + print(f"\nFinal objective value: {problem.ObjValue:.2f}") print( f"\nTotal incumbent solutions found: " diff --git a/docs/cuopt/source/cuopt-python/lp-qp-milp/lp-qp-milp-examples.rst b/docs/cuopt/source/cuopt-python/lp-qp-milp/lp-qp-milp-examples.rst index 920f594a0b..74c9439296 100644 --- a/docs/cuopt/source/cuopt-python/lp-qp-milp/lp-qp-milp-examples.rst +++ b/docs/cuopt/source/cuopt-python/lp-qp-milp/lp-qp-milp-examples.rst @@ -166,14 +166,18 @@ The response is as follows: .. code-block:: text - Incumbent 1: x=36.0, y=41.0, cost: 303.00 + Optimal solution found. + Incumbent 1: x=36.0 y=41.0 cost: 303.00 + Solution objective: 303.000000 , relative_mip_gap 0.000000 solution_bound 303.000000 presolve_time 0.103659 total_solve_time 0.173678 max constraint violation 0.000000 max int violation 0.000000 max var bounds violation 0.000000 nodes 0 simplex_iterations 2 === Final Results === Problem status: Optimal - Solve time: 0.27 seconds - Final solution: x=36.0, y=41.0 + Solve time: 0.17 seconds + Final solution: x=36.0 y=41.0 Final objective value: 303.00 + Total incumbent solutions found: 1 + Working with PDLP Warmstart Data -------------------------------- From 9dd89fce19a15724f2e98f693ee480c799f1c717 Mon Sep 17 00:00:00 2001 From: Ishika Roy Date: Fri, 20 Mar 2026 10:51:43 -0700 Subject: [PATCH 4/4] formatting --- .../examples/incumbent_solutions_example.py | 20 ++++++++----------- .../lp-qp-milp/lp-qp-milp-examples.rst | 2 +- .../assets/milp_basic/incumbent_callback.py | 4 +--- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/docs/cuopt/source/cuopt-python/lp-qp-milp/examples/incumbent_solutions_example.py b/docs/cuopt/source/cuopt-python/lp-qp-milp/examples/incumbent_solutions_example.py index ce84a7f453..1ef73669d3 100644 --- a/docs/cuopt/source/cuopt-python/lp-qp-milp/examples/incumbent_solutions_example.py +++ b/docs/cuopt/source/cuopt-python/lp-qp-milp/examples/incumbent_solutions_example.py @@ -64,9 +64,7 @@ def get_solution(self, solution, solution_cost, solution_bound, user_data): self.n_callbacks += 1 # Use getIncumbentValues to extract values for specific variables - values = self.problem.getIncumbentValues( - solution, self.variables - ) + values = self.problem.getIncumbentValues(solution, self.variables) incumbent = { "values": values, @@ -76,9 +74,9 @@ def get_solution(self, solution, solution_cost, solution_bound, user_data): } self.solutions.append(incumbent) - print(f"Incumbent {self.n_callbacks}:", end=' ') + print(f"Incumbent {self.n_callbacks}:", end=" ") for i, var in enumerate(self.variables): - print(f"{var.VariableName}={values[i]}", end=' ') + print(f"{var.VariableName}={values[i]}", end=" ") print(f"cost: {incumbent['cost']:.2f}") @@ -87,8 +85,8 @@ def main(): problem = Problem("Incumbent Example") # Add integer variables - x = problem.addVariable(vtype=INTEGER, name='x') - y = problem.addVariable(vtype=INTEGER, name='y') + x = problem.addVariable(vtype=INTEGER, name="x") + y = problem.addVariable(vtype=INTEGER, name="y") # Add constraints problem.addConstraint(2 * x + 4 * y >= 230) @@ -100,9 +98,7 @@ def main(): # Configure solver settings with callback settings = SolverSettings() user_data = {"source": "incumbent_solutions_example"} - incumbent_callback = IncumbentCallback( - problem, [x, y], user_data - ) + incumbent_callback = IncumbentCallback(problem, [x, y], user_data) settings.set_mip_callback(incumbent_callback, user_data) settings.set_parameter(CUOPT_TIME_LIMIT, 30) @@ -113,9 +109,9 @@ def main(): print("\n=== Final Results ===") print(f"Problem status: {problem.Status.name}") print(f"Solve time: {problem.SolveTime:.2f} seconds") - print(f"Final solution: ", end=' ') + print("Final solution: ", end=" ") for i, var in enumerate(problem.getVariables()): - print(f"{var.VariableName}={var.getValue()} ", end=' ') + print(f"{var.VariableName}={var.getValue()} ", end=" ") print(f"\nFinal objective value: {problem.ObjValue:.2f}") print( diff --git a/docs/cuopt/source/cuopt-python/lp-qp-milp/lp-qp-milp-examples.rst b/docs/cuopt/source/cuopt-python/lp-qp-milp/lp-qp-milp-examples.rst index 74c9439296..2edd625fbc 100644 --- a/docs/cuopt/source/cuopt-python/lp-qp-milp/lp-qp-milp-examples.rst +++ b/docs/cuopt/source/cuopt-python/lp-qp-milp/lp-qp-milp-examples.rst @@ -173,7 +173,7 @@ The response is as follows: === Final Results === Problem status: Optimal Solve time: 0.17 seconds - Final solution: x=36.0 y=41.0 + Final solution: x=36.0 y=41.0 Final objective value: 303.00 Total incumbent solutions found: 1 diff --git a/skills/cuopt-lp-milp-api-python/assets/milp_basic/incumbent_callback.py b/skills/cuopt-lp-milp-api-python/assets/milp_basic/incumbent_callback.py index e36d0b22b6..38f553f7e1 100644 --- a/skills/cuopt-lp-milp-api-python/assets/milp_basic/incumbent_callback.py +++ b/skills/cuopt-lp-milp-api-python/assets/milp_basic/incumbent_callback.py @@ -22,9 +22,7 @@ def __init__(self, problem, variables, user_data): def get_solution(self, solution, solution_cost, solution_bound, user_data): self.n_callbacks += 1 - values = self.problem.getIncumbentValues( - solution, self.variables - ) + values = self.problem.getIncumbentValues(solution, self.variables) cost = float(solution_cost[0]) vals_str = ", ".join(f"{float(v)}" for v in values) print(f"Incumbent {self.n_callbacks}: [{vals_str}], cost: {cost:.2f}")