diff --git a/python/cuopt/cuopt/linear_programming/problem.py b/python/cuopt/cuopt/linear_programming/problem.py index baf9716191..6f97a11559 100644 --- a/python/cuopt/cuopt/linear_programming/problem.py +++ b/python/cuopt/cuopt/linear_programming/problem.py @@ -1900,7 +1900,11 @@ def relax(self): def populate_solution(self, solution): self.Status = solution.get_termination_status() self.SolveTime = solution.get_solve_time() - self.warmstart_data = solution.get_pdlp_warm_start_data() + self.warmstart_data = ( + solution.get_pdlp_warm_start_data() + if solution.problem_category == 0 + else None + ) IsMIP = False if solution.problem_category == 0: @@ -1909,7 +1913,7 @@ def populate_solution(self, solution): IsMIP = True self.SolutionStats = self.dict_to_object(solution.get_milp_stats()) primal_sol = solution.get_primal_solution() - reduced_cost = solution.get_reduced_cost() + reduced_cost = solution.get_reduced_cost() if not IsMIP else None if len(primal_sol) > 0: for var in self.vars: var.Value = primal_sol[var.index] diff --git a/python/cuopt/cuopt/linear_programming/solution/solution.py b/python/cuopt/cuopt/linear_programming/solution/solution.py index 849c907d06..e2533da8c1 100644 --- a/python/cuopt/cuopt/linear_programming/solution/solution.py +++ b/python/cuopt/cuopt/linear_programming/solution/solution.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 from cuopt.linear_programming.solver.solver_wrapper import ( @@ -167,25 +167,28 @@ def __init__( self.problem_category = problem_category self.primal_solution = primal_solution self.dual_solution = dual_solution - self.pdlp_warm_start_data = PDLPWarmStartData( - current_primal_solution, - current_dual_solution, - initial_primal_average, - initial_dual_average, - current_ATY, - sum_primal_solutions, - sum_dual_solutions, - last_restart_duality_gap_primal_solution, - last_restart_duality_gap_dual_solution, - initial_primal_weight, - initial_step_size, - total_pdlp_iterations, - total_pdhg_iterations, - last_candidate_kkt_score, - last_restart_kkt_score, - sum_solution_weight, - iterations_since_last_restart, - ) + if problem_category == ProblemCategory.LP: + self.pdlp_warm_start_data = PDLPWarmStartData( + current_primal_solution, + current_dual_solution, + initial_primal_average, + initial_dual_average, + current_ATY, + sum_primal_solutions, + sum_dual_solutions, + last_restart_duality_gap_primal_solution, + last_restart_duality_gap_dual_solution, + initial_primal_weight, + initial_step_size, + total_pdlp_iterations, + total_pdhg_iterations, + last_candidate_kkt_score, + last_restart_kkt_score, + sum_solution_weight, + iterations_since_last_restart, + ) + else: + self.pdlp_warm_start_data = None self._set_termination_status(termination_status) self.error_status = error_status self.error_message = error_message @@ -216,8 +219,17 @@ def __init__( def _set_termination_status(self, ts): if self.problem_category == ProblemCategory.LP: self.termination_status = LPTerminationStatus(ts) - else: + elif self.problem_category in ( + ProblemCategory.MIP, + ProblemCategory.IP, + ): self.termination_status = MILPTerminationStatus(ts) + else: + raise ValueError( + f"Unknown problem_category: {self.problem_category!r}. " + "Expected one of ProblemCategory.LP, ProblemCategory.MIP, " + "ProblemCategory.IP." + ) def raise_if_milp_solution(self, function_name): if self.problem_category in (ProblemCategory.MIP, ProblemCategory.IP): @@ -242,7 +254,7 @@ def get_dual_solution(self): Note: Applicable to only LP Returns the dual solution as numpy.array with float64 type. """ - self.raise_if_milp_solution(__name__) + self.raise_if_milp_solution("get_dual_solution") return self.dual_solution def get_primal_objective(self): @@ -256,7 +268,7 @@ def get_dual_objective(self): Note: Applicable to only LP Returns the dual objective as a float64. """ - self.raise_if_milp_solution(__name__) + self.raise_if_milp_solution("get_dual_objective") return self.dual_objective def get_termination_status(self): @@ -325,14 +337,16 @@ def get_lp_stats(self): Number of iterations the LP solver did before converging. """ - self.raise_if_milp_solution(__name__) + self.raise_if_milp_solution("get_lp_stats") return self.lp_stats def get_reduced_cost(self): """ + Note: Applicable to only LP Returns the reduced cost as numpy.array with float64 type. """ + self.raise_if_milp_solution("get_reduced_cost") return self.reduced_cost def get_pdlp_warm_start_data(self): @@ -343,6 +357,7 @@ def get_pdlp_warm_start_data(self): See `SolverSettings.set_pdlp_warm_start_data` for more details. """ + self.raise_if_milp_solution("get_pdlp_warm_start_data") return self.pdlp_warm_start_data def get_milp_stats(self): @@ -386,7 +401,7 @@ def get_milp_stats(self): Number of simplex iterations performed during the MIP solve """ - self.raise_if_lp_solution(__name__) + self.raise_if_lp_solution("get_milp_stats") return self.milp_stats