From 52aae28a6d84bea48f432d6f7c1f52f6d425f98a Mon Sep 17 00:00:00 2001 From: Fabrizio Finozzi Date: Tue, 24 Mar 2026 09:52:42 +0100 Subject: [PATCH 1/8] code: re-introduce knitro context closure and export solver quantities --- linopy/solvers.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/linopy/solvers.py b/linopy/solvers.py index 10731547..cd6b4342 100644 --- a/linopy/solvers.py +++ b/linopy/solvers.py @@ -1747,7 +1747,7 @@ def get_solver_solution() -> Solution: return Result(status, solution, m) -KnitroResult = namedtuple("KnitroResult", "knitro_context reported_runtime") +KnitroResult = namedtuple("KnitroResult", "reported_runtime mip_relaxation_bnd mip_number_nodes mip_number_solves mip_rel_gap mip_abs_gap abs_feas_error rel_feas_error abs_opt_error rel_opt_error", ) class Knitro(Solver[None]): @@ -1938,15 +1938,31 @@ def get_solver_solution() -> Solution: solution_fn.parent.mkdir(exist_ok=True) knitro.KN_write_mps_file(kc, path_to_string(solution_fn)) + mip_relaxation_bnd = knitro.KN_get_mip_relaxation_bnd(kc) + mip_number_nodes = knitro.KN_get_mip_number_nodes(kc) + mip_number_solves = knitro.KN_get_mip_number_solves(kc) + mip_rel_gap = knitro.KN_get_mip_rel_gap(kc) + mip_abs_gap = knitro.KN_get_mip_abs_gap(kc) + abs_feas_error = knitro.KN_get_abs_feas_error(kc) + rel_feas_error = knitro.KN_get_rel_feas_error(kc) + abs_opt_error = knitro.KN_get_abs_opt_error(kc) + rel_opt_error = knitro.KN_get_rel_opt_error(kc) + return Result( - status, - solution, - KnitroResult(knitro_context=kc, reported_runtime=reported_runtime), + status, solution, KnitroResult(reported_runtime=reported_runtime, + mip_relaxation_bnd=mip_relaxation_bnd, + mip_number_nodes=mip_number_nodes, + mip_number_solves=mip_number_solves, + mip_rel_gap=mip_rel_gap, + mip_abs_gap=mip_abs_gap, + abs_feas_error=abs_feas_error, + rel_feas_error=rel_feas_error, + abs_opt_error=abs_opt_error, + rel_opt_error=rel_opt_error) ) - finally: - # Intentionally keep the Knitro context alive; do not free `kc` here. - pass + with contextlib.suppress(Exception): + knitro.KN_free(kc) mosek_bas_re = re.compile(r" (XL|XU)\s+([^ \t]+)\s+([^ \t]+)| (LL|UL|BS)\s+([^ \t]+)") From f5ea93c2d353f0a269d8ab94a76ec25bc0b45bf2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Mar 2026 09:24:58 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- linopy/solvers.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/linopy/solvers.py b/linopy/solvers.py index cd6b4342..abe13cd4 100644 --- a/linopy/solvers.py +++ b/linopy/solvers.py @@ -1747,7 +1747,10 @@ def get_solver_solution() -> Solution: return Result(status, solution, m) -KnitroResult = namedtuple("KnitroResult", "reported_runtime mip_relaxation_bnd mip_number_nodes mip_number_solves mip_rel_gap mip_abs_gap abs_feas_error rel_feas_error abs_opt_error rel_opt_error", ) +KnitroResult = namedtuple( + "KnitroResult", + "reported_runtime mip_relaxation_bnd mip_number_nodes mip_number_solves mip_rel_gap mip_abs_gap abs_feas_error rel_feas_error abs_opt_error rel_opt_error", +) class Knitro(Solver[None]): @@ -1949,16 +1952,20 @@ def get_solver_solution() -> Solution: rel_opt_error = knitro.KN_get_rel_opt_error(kc) return Result( - status, solution, KnitroResult(reported_runtime=reported_runtime, - mip_relaxation_bnd=mip_relaxation_bnd, - mip_number_nodes=mip_number_nodes, - mip_number_solves=mip_number_solves, - mip_rel_gap=mip_rel_gap, - mip_abs_gap=mip_abs_gap, - abs_feas_error=abs_feas_error, - rel_feas_error=rel_feas_error, - abs_opt_error=abs_opt_error, - rel_opt_error=rel_opt_error) + status, + solution, + KnitroResult( + reported_runtime=reported_runtime, + mip_relaxation_bnd=mip_relaxation_bnd, + mip_number_nodes=mip_number_nodes, + mip_number_solves=mip_number_solves, + mip_rel_gap=mip_rel_gap, + mip_abs_gap=mip_abs_gap, + abs_feas_error=abs_feas_error, + rel_feas_error=rel_feas_error, + abs_opt_error=abs_opt_error, + rel_opt_error=rel_opt_error, + ), ) finally: with contextlib.suppress(Exception): From a3e70bb6af1737899778fb5142a9cb4ddc1a5a97 Mon Sep 17 00:00:00 2001 From: Fabrizio Finozzi Date: Tue, 24 Mar 2026 10:33:58 +0100 Subject: [PATCH 3/8] code: place quantities calculation elsewhere --- linopy/solvers.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/linopy/solvers.py b/linopy/solvers.py index cd6b4342..eebbba96 100644 --- a/linopy/solvers.py +++ b/linopy/solvers.py @@ -1895,8 +1895,26 @@ def solve_problem_from_file( ret = int(knitro.KN_solve(kc)) reported_runtime: float | None = None + mip_relaxation_bnd: float | None = None + mip_number_nodes: int | None = None + mip_number_solves: int | None = None + mip_rel_gap: float | None = None + mip_abs_gap: float | None = None + abs_feas_error: float | None = None + rel_feas_error: float | None = None + abs_opt_error: float | None = None + rel_opt_error: float | None = None with contextlib.suppress(Exception): reported_runtime = float(knitro.KN_get_solve_time_real(kc)) + mip_relaxation_bnd = float(knitro.KN_get_mip_relaxation_bnd(kc)) + mip_number_nodes = int(knitro.KN_get_mip_number_nodes(kc)) + mip_number_solves = int(knitro.KN_get_mip_number_solves(kc)) + mip_rel_gap = float(knitro.KN_get_mip_rel_gap(kc)) + mip_abs_gap = float(knitro.KN_get_mip_abs_gap(kc)) + abs_feas_error = float(knitro.KN_get_abs_feas_error(kc)) + rel_feas_error = float(knitro.KN_get_rel_feas_error(kc)) + abs_opt_error = float(knitro.KN_get_abs_opt_error(kc)) + rel_opt_error = float(knitro.KN_get_rel_opt_error(kc)) if ret in CONDITION_MAP: termination_condition = CONDITION_MAP[ret] @@ -1938,16 +1956,6 @@ def get_solver_solution() -> Solution: solution_fn.parent.mkdir(exist_ok=True) knitro.KN_write_mps_file(kc, path_to_string(solution_fn)) - mip_relaxation_bnd = knitro.KN_get_mip_relaxation_bnd(kc) - mip_number_nodes = knitro.KN_get_mip_number_nodes(kc) - mip_number_solves = knitro.KN_get_mip_number_solves(kc) - mip_rel_gap = knitro.KN_get_mip_rel_gap(kc) - mip_abs_gap = knitro.KN_get_mip_abs_gap(kc) - abs_feas_error = knitro.KN_get_abs_feas_error(kc) - rel_feas_error = knitro.KN_get_rel_feas_error(kc) - abs_opt_error = knitro.KN_get_abs_opt_error(kc) - rel_opt_error = knitro.KN_get_rel_opt_error(kc) - return Result( status, solution, KnitroResult(reported_runtime=reported_runtime, mip_relaxation_bnd=mip_relaxation_bnd, From 23b848be468c5e65c0a2c253e8e05ff2448c840a Mon Sep 17 00:00:00 2001 From: Fabrizio Finozzi Date: Tue, 24 Mar 2026 10:34:12 +0100 Subject: [PATCH 4/8] code: place quantities calculation elsewhere --- linopy/solvers.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/linopy/solvers.py b/linopy/solvers.py index eebbba96..216a83fb 100644 --- a/linopy/solvers.py +++ b/linopy/solvers.py @@ -1747,7 +1747,10 @@ def get_solver_solution() -> Solution: return Result(status, solution, m) -KnitroResult = namedtuple("KnitroResult", "reported_runtime mip_relaxation_bnd mip_number_nodes mip_number_solves mip_rel_gap mip_abs_gap abs_feas_error rel_feas_error abs_opt_error rel_opt_error", ) +KnitroResult = namedtuple( + "KnitroResult", + "reported_runtime mip_relaxation_bnd mip_number_nodes mip_number_solves mip_rel_gap mip_abs_gap abs_feas_error rel_feas_error abs_opt_error rel_opt_error", +) class Knitro(Solver[None]): @@ -1957,16 +1960,20 @@ def get_solver_solution() -> Solution: knitro.KN_write_mps_file(kc, path_to_string(solution_fn)) return Result( - status, solution, KnitroResult(reported_runtime=reported_runtime, - mip_relaxation_bnd=mip_relaxation_bnd, - mip_number_nodes=mip_number_nodes, - mip_number_solves=mip_number_solves, - mip_rel_gap=mip_rel_gap, - mip_abs_gap=mip_abs_gap, - abs_feas_error=abs_feas_error, - rel_feas_error=rel_feas_error, - abs_opt_error=abs_opt_error, - rel_opt_error=rel_opt_error) + status, + solution, + KnitroResult( + reported_runtime=reported_runtime, + mip_relaxation_bnd=mip_relaxation_bnd, + mip_number_nodes=mip_number_nodes, + mip_number_solves=mip_number_solves, + mip_rel_gap=mip_rel_gap, + mip_abs_gap=mip_abs_gap, + abs_feas_error=abs_feas_error, + rel_feas_error=rel_feas_error, + abs_opt_error=abs_opt_error, + rel_opt_error=rel_opt_error, + ), ) finally: with contextlib.suppress(Exception): From 1a30d459e0ef777844179a46a84e138420303359 Mon Sep 17 00:00:00 2001 From: Fabrizio Finozzi Date: Wed, 25 Mar 2026 10:24:12 +0100 Subject: [PATCH 5/8] code: add new quantities extracted from knitro context --- linopy/solvers.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/linopy/solvers.py b/linopy/solvers.py index 216a83fb..9b77a90a 100644 --- a/linopy/solvers.py +++ b/linopy/solvers.py @@ -1749,7 +1749,7 @@ def get_solver_solution() -> Solution: KnitroResult = namedtuple( "KnitroResult", - "reported_runtime mip_relaxation_bnd mip_number_nodes mip_number_solves mip_rel_gap mip_abs_gap abs_feas_error rel_feas_error abs_opt_error rel_opt_error", + "reported_runtime mip_relaxation_bnd mip_number_nodes mip_number_solves mip_rel_gap mip_abs_gap abs_feas_error rel_feas_error abs_opt_error rel_opt_error n_vars n_cons n_integer_vars n_continuous_vars", ) @@ -1907,6 +1907,10 @@ def solve_problem_from_file( rel_feas_error: float | None = None abs_opt_error: float | None = None rel_opt_error: float | None = None + n_vars: int | None = None + n_cons: int | None = None + n_integer_vars: int | None = None + n_continuous_vars: int | None = None with contextlib.suppress(Exception): reported_runtime = float(knitro.KN_get_solve_time_real(kc)) mip_relaxation_bnd = float(knitro.KN_get_mip_relaxation_bnd(kc)) @@ -1918,6 +1922,13 @@ def solve_problem_from_file( rel_feas_error = float(knitro.KN_get_rel_feas_error(kc)) abs_opt_error = float(knitro.KN_get_abs_opt_error(kc)) rel_opt_error = float(knitro.KN_get_rel_opt_error(kc)) + n_vars = int(knitro.KN_get_number_vars(kc)) + n_cons = int(knitro.KN_get_number_cons(kc)) + var_types = list(knitro.KN_get_var_types(kc)) + n_integer_vars = var_types.count( + knitro.KN_VARTYPE_INTEGER + ) + var_types.count(knitro.KN_VARTYPE_BINARY) + n_continuous_vars = var_types.count(knitro.KN_VARTYPE_CONTINUOUS) if ret in CONDITION_MAP: termination_condition = CONDITION_MAP[ret] @@ -1973,6 +1984,10 @@ def get_solver_solution() -> Solution: rel_feas_error=rel_feas_error, abs_opt_error=abs_opt_error, rel_opt_error=rel_opt_error, + n_vars=n_vars, + n_cons=n_cons, + n_integer_vars=n_integer_vars, + n_continuous_vars=n_continuous_vars, ), ) finally: From 6ae96fd563aa7e212b276360aa1942769f5210a4 Mon Sep 17 00:00:00 2001 From: Fabrizio Finozzi Date: Wed, 25 Mar 2026 10:28:16 +0100 Subject: [PATCH 6/8] code: add int --- linopy/solvers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/linopy/solvers.py b/linopy/solvers.py index 9b77a90a..ac4a03cd 100644 --- a/linopy/solvers.py +++ b/linopy/solvers.py @@ -1925,10 +1925,10 @@ def solve_problem_from_file( n_vars = int(knitro.KN_get_number_vars(kc)) n_cons = int(knitro.KN_get_number_cons(kc)) var_types = list(knitro.KN_get_var_types(kc)) - n_integer_vars = var_types.count( + n_integer_vars = int(var_types.count( knitro.KN_VARTYPE_INTEGER - ) + var_types.count(knitro.KN_VARTYPE_BINARY) - n_continuous_vars = var_types.count(knitro.KN_VARTYPE_CONTINUOUS) + ) + var_types.count(knitro.KN_VARTYPE_BINARY)) + n_continuous_vars = int(var_types.count(knitro.KN_VARTYPE_CONTINUOUS)) if ret in CONDITION_MAP: termination_condition = CONDITION_MAP[ret] From 82b8550915ff963b6a2e39af37a5a14a2d9b7fc8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:28:29 +0000 Subject: [PATCH 7/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- linopy/solvers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/linopy/solvers.py b/linopy/solvers.py index ac4a03cd..fb04e476 100644 --- a/linopy/solvers.py +++ b/linopy/solvers.py @@ -1925,9 +1925,10 @@ def solve_problem_from_file( n_vars = int(knitro.KN_get_number_vars(kc)) n_cons = int(knitro.KN_get_number_cons(kc)) var_types = list(knitro.KN_get_var_types(kc)) - n_integer_vars = int(var_types.count( - knitro.KN_VARTYPE_INTEGER - ) + var_types.count(knitro.KN_VARTYPE_BINARY)) + n_integer_vars = int( + var_types.count(knitro.KN_VARTYPE_INTEGER) + + var_types.count(knitro.KN_VARTYPE_BINARY) + ) n_continuous_vars = int(var_types.count(knitro.KN_VARTYPE_CONTINUOUS)) if ret in CONDITION_MAP: From 8559ecef103cc58f87a29b4e004b518116fd23b7 Mon Sep 17 00:00:00 2001 From: Fabrizio Finozzi Date: Wed, 25 Mar 2026 13:03:46 +0100 Subject: [PATCH 8/8] code: add release notes --- doc/release_notes.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 26007b5c..3e610fff 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -24,6 +24,7 @@ Upcoming Version * Improve handling of CPLEX solver quality attributes to ensure metrics such are extracted correctly when available. * Fix Xpress IIS label mapping for masked constraints and add a regression test for matching infeasible coordinates. * Enable quadratic problems with SCIP on windows. +* Free the knitro context and compute necessary quantities within linopy. Knitro context is not exposed anymore. Version 0.6.5