Skip to content

Commit 5df0a00

Browse files
authored
Merge pull request #3595 from mrmundt/issue-3579
Resolve GAMS/Windows logfile name issue
2 parents d82b87f + 3624e0f commit 5df0a00

File tree

2 files changed

+54
-7
lines changed

2 files changed

+54
-7
lines changed

pyomo/solvers/plugins/solvers/GAMS.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from pyomo.opt import ProblemFormat, SolverFactory
1919

2020
import pyomo.common
21+
from pyomo.common.dependencies import pathlib
2122
from pyomo.common.collections import Bunch
2223
from pyomo.common.tee import TeeStream
2324

@@ -300,7 +301,7 @@ def solve(self, *args, **kwds):
300301
filename=output_file,
301302
format=ProblemFormat.gams,
302303
_called_by_solver=True,
303-
**io_options
304+
**io_options,
304305
)
305306
symbolMap = getattr(model, "._symbol_maps")[smap_id]
306307
else:
@@ -739,6 +740,32 @@ def _parse_special_values(value):
739740
return sys.float_info.epsilon
740741
return value
741742

743+
def _rewrite_path_win8p3(self, path):
744+
"""
745+
Return the 8.3 short path on Windows; unchanged elsewhere.
746+
747+
This change is in response to Pyomo/pyomo#3579 which reported
748+
that GAMS (direct) fails on Windows if there is a space in
749+
the path. This utility converts paths to their 8.3 short-path version
750+
(which never have spaces).
751+
"""
752+
if not sys.platform.startswith("win"):
753+
return str(path)
754+
755+
import ctypes, ctypes.wintypes as wt
756+
757+
GetShortPathNameW = ctypes.windll.kernel32.GetShortPathNameW
758+
GetShortPathNameW.argtypes = [wt.LPCWSTR, wt.LPWSTR, wt.DWORD]
759+
760+
# the file must exist, or Windows will not create a short name
761+
pathlib.Path(path).parent.mkdir(parents=True, exist_ok=True)
762+
pathlib.Path(path).touch(exist_ok=True)
763+
764+
buf = ctypes.create_unicode_buffer(260)
765+
if GetShortPathNameW(str(path), buf, 260):
766+
return buf.value
767+
return str(path)
768+
742769
def solve(self, *args, **kwds):
743770
"""
744771
Solve a model via the GAMS executable.
@@ -845,7 +872,7 @@ def solve(self, *args, **kwds):
845872
filename=output_filename,
846873
format=ProblemFormat.gams,
847874
_called_by_solver=True,
848-
**io_options
875+
**io_options,
849876
)
850877
symbolMap = getattr(model, "._symbol_maps")[smap_id]
851878
else:
@@ -881,7 +908,7 @@ def solve(self, *args, **kwds):
881908
elif tee and logfile:
882909
command.append("lo=4")
883910
if logfile:
884-
command.append("lf=" + str(logfile))
911+
command.append(f"lf={self._rewrite_path_win8p3(logfile)}")
885912

886913
try:
887914
ostreams = [StringIO()]

pyomo/solvers/tests/checks/test_GAMS.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ def tearDown(self):
355355
"""Clean up temporary directory after tests are over."""
356356
shutil.rmtree(self.tmpdir)
357357

358-
def _check_logfile(self, exists=True):
358+
def _check_logfile(self, exists=True, logfile=None):
359359
"""Check for logfiles existence and contents.
360360
361361
exists=True:
@@ -364,12 +364,14 @@ def _check_logfile(self, exists=True):
364364
Optionally check that the logfiles contents is equal to this value.
365365
366366
"""
367+
if not logfile:
368+
logfile = self.logfile
367369
if not exists:
368-
self.assertFalse(os.path.exists(self.logfile))
370+
self.assertFalse(os.path.exists(logfile))
369371
return
370372

371-
self.assertTrue(os.path.exists(self.logfile))
372-
with open(self.logfile) as f:
373+
self.assertTrue(os.path.exists(logfile))
374+
with open(logfile) as f:
373375
logfile_contents = f.read()
374376
self.assertIn(self.characteristic_output_string, logfile_contents)
375377

@@ -416,6 +418,15 @@ def test_logfile(self):
416418
self._check_stdout(output.getvalue(), exists=False)
417419
self._check_logfile(exists=True)
418420

421+
def test_logfile_with_spaces(self):
422+
# In response to Issue 3579
423+
logfile_with_spaces = os.path.join(self.tmpdir, "My File.log")
424+
with SolverFactory("gams", solver_io="gms") as opt:
425+
with capture_output() as output:
426+
opt.solve(self.m, logfile=logfile_with_spaces)
427+
self._check_stdout(output.getvalue(), exists=False)
428+
self._check_logfile(exists=True, logfile=logfile_with_spaces)
429+
419430
def test_logfile_relative(self):
420431
cwd = os.getcwd()
421432
with TempfileManager:
@@ -470,6 +481,15 @@ def test_logfile(self):
470481
self._check_stdout(output.getvalue(), exists=False)
471482
self._check_logfile(exists=True)
472483

484+
def test_logfile_with_spaces(self):
485+
# In response to Issue 3579
486+
logfile_with_spaces = os.path.join(self.tmpdir, "My File.log")
487+
with SolverFactory("gams", solver_io="gms") as opt:
488+
with capture_output() as output:
489+
opt.solve(self.m, logfile=logfile_with_spaces)
490+
self._check_stdout(output.getvalue(), exists=False)
491+
self._check_logfile(exists=True, logfile=logfile_with_spaces)
492+
473493
def test_logfile_relative(self):
474494
cwd = os.getcwd()
475495
with TempfileManager:

0 commit comments

Comments
 (0)