From 0022a9bc93a62bfb3ad2872a62c477fabc8e5a1c Mon Sep 17 00:00:00 2001 From: eminyouskn Date: Fri, 19 Jun 2026 11:36:30 -0400 Subject: [PATCH 1/3] KNITROAMPL: prefer \$KNITRODIR/knitroampl/knitroampl for executable lookup Add \$KNITRODIR/knitroampl/knitroampl as the first candidate in _default_executable(), before the knitro Python package path and the system PATH fallback. This allows users with a standalone Knitro installation (no Python package) to be picked up automatically via the KNITRODIR environment variable. --- pyomo/solvers/plugins/solvers/KNITROAMPL.py | 31 ++++++++++++++------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/pyomo/solvers/plugins/solvers/KNITROAMPL.py b/pyomo/solvers/plugins/solvers/KNITROAMPL.py index 930876d7ac6..e4aa659b446 100644 --- a/pyomo/solvers/plugins/solvers/KNITROAMPL.py +++ b/pyomo/solvers/plugins/solvers/KNITROAMPL.py @@ -7,6 +7,7 @@ # software. This software is distributed under the 3-clause BSD License. # ____________________________________________________________________________________ import logging +import os from pyomo.common import Executable from pyomo.common.collections import Bunch @@ -64,17 +65,27 @@ def __init__(self, **kwds): self._capabilities.sos2 = False def _default_executable(self): - try: - # If knitro Python package is available, use the executable it contains - import knitro - - package_knitroampl_path = ( - pathlib.Path(knitro.__file__).resolve().parent - / 'knitroampl' - / 'knitroampl' + executable = None + knitrodir = os.environ.get('KNITRODIR') + if knitrodir: + knitroampl_path = ( + pathlib.Path(knitrodir) / 'knitroampl' / 'knitroampl' ) - executable = Executable(str(package_knitroampl_path)) - except ModuleNotFoundError: + executable = Executable(str(knitroampl_path)) + if not executable: + try: + # If knitro Python package is available, use the executable it contains + import knitro + + package_knitroampl_path = ( + pathlib.Path(knitro.__file__).resolve().parent + / 'knitroampl' + / 'knitroampl' + ) + executable = Executable(str(package_knitroampl_path)) + except ModuleNotFoundError: + pass + if not executable: # Otherwise, search usual path list executable = Executable('knitroampl') if not executable: From b739ff96a3af4db9a997a4e0628f934f70f12077 Mon Sep 17 00:00:00 2001 From: eminyouskn Date: Fri, 19 Jun 2026 11:49:13 -0400 Subject: [PATCH 2/3] black format --- pyomo/solvers/plugins/solvers/KNITROAMPL.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyomo/solvers/plugins/solvers/KNITROAMPL.py b/pyomo/solvers/plugins/solvers/KNITROAMPL.py index e4aa659b446..0d01418b4c2 100644 --- a/pyomo/solvers/plugins/solvers/KNITROAMPL.py +++ b/pyomo/solvers/plugins/solvers/KNITROAMPL.py @@ -68,9 +68,7 @@ def _default_executable(self): executable = None knitrodir = os.environ.get('KNITRODIR') if knitrodir: - knitroampl_path = ( - pathlib.Path(knitrodir) / 'knitroampl' / 'knitroampl' - ) + knitroampl_path = pathlib.Path(knitrodir) / 'knitroampl' / 'knitroampl' executable = Executable(str(knitroampl_path)) if not executable: try: From 099d8b0ef9bb7cba56213816f922a6e691a176dc Mon Sep 17 00:00:00 2001 From: Miranda Mundt Date: Tue, 25 Aug 2026 12:37:17 -0600 Subject: [PATCH 3/3] Add a small test --- pyomo/solvers/tests/checks/test_KNITROAMPL.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pyomo/solvers/tests/checks/test_KNITROAMPL.py b/pyomo/solvers/tests/checks/test_KNITROAMPL.py index d9ab6f0cdb5..c762bdb88a8 100644 --- a/pyomo/solvers/tests/checks/test_KNITROAMPL.py +++ b/pyomo/solvers/tests/checks/test_KNITROAMPL.py @@ -7,7 +7,10 @@ # software. This software is distributed under the 3-clause BSD License. # ____________________________________________________________________________________ +import os + from pyomo.common import unittest +from pyomo.common.tempfiles import TempfileManager from pyomo.environ import ( ConcreteModel, Var, @@ -23,6 +26,32 @@ knitroampl_available = SolverFactory('knitroampl').available(False) +class TestKNITROAMPLDefaultExecutable(unittest.TestCase): + def test_default_executable_from_KNITRODIR(self): + with TempfileManager.new_context() as tempfile: + knitrodir = tempfile.create_tempdir() + exe = os.path.join(knitrodir, 'knitroampl', 'knitroampl') + # This makes a fake executable with the correct permissions + # so KNITRO actually recognizes it + os.mkdir(os.path.dirname(exe)) + with open(exe, 'w'): + pass + os.chmod(exe, 0o755) + + orig = os.environ.get('KNITRODIR') + os.environ['KNITRODIR'] = knitrodir + try: + opt = SolverFactory('knitroampl') + self.assertEqual( + os.path.realpath(opt._default_executable()), os.path.realpath(exe) + ) + finally: + if orig is None: + del os.environ['KNITRODIR'] + else: + os.environ['KNITRODIR'] = orig + + @unittest.skipIf(not knitroampl_available, "The 'knitroampl' command is not available") @unittest.pytest.mark.solver("knitroampl") class TestKNITROAMPLInterface(unittest.TestCase):