Repository: Pyomo/pyomo

Issue:
GAMS Shell Solver `logfile` Option Behavior Differs from that of Other Solvers
## Summary
The `solve` method for a typical Pyomo solver interface provides an optional argument `logfile`, meant to be a desired path to which to write the solver log. A handful of interfaces (such as those of BARON and IPOPT) write the logfile to the specified path. However, the GAMS shell solver interface writes this logfile to the temporary working directory used by the solver. The temporary directory is removed unless `keepfiles=True`. 

<!-- Explain, in a clear and concise way, the command you ran and the result you were trying to achieve.
Example: "I ran `pyomo solve` to optimize a model and ..." -->

### Steps to reproduce the issue
1. Install local BARON, IPOPT, and GAMS executables.
2. Simple working example script:

<!-- Please include the command run and/or provide an example script that demonstrates the problem. -->

```python
import pyomo.environ as pyo


m = pyo.ConcreteModel()

m.S = pyo.Set(initialize=list(range(5)))
m.x = pyo.Var(m.S, bounds=(0, 1), initialize=1)
m.obj = pyo.Objective(expr=2000 * pyo.summation(m.x), sense=pyo.maximize)

ipopt = pyo.SolverFactory("ipopt")
baron = pyo.SolverFactory("baron")
gams = pyo.SolverFactory("gams:ipopt")

# ipopt and baron solve. logfiles written to working directory
ipopt.solve(m, logfile="ipopt_log.log")
baron.solve(m, logfile="baron_log.log")

# logfile written to automatically generated
# temporary working directory, along with other
# output files. since keepfiles=False, the directory
# is deleted.
gams.solve(
    m,
    keepfiles=False,
    logfile="gams_logfile.txt",
    tmpdir=None,
)

# logfile written to automatically generated
# temporary working directory, along with other
# output files. path to the temporary directory
# is displayed in console output
gams.solve(
    m,
    keepfiles=True,
    logfile="gams_logfile.txt",
    tmpdir=None,
)

# logfile written to automatically generated
# temporary working directory, along with other
# output files. since keepfiles=False, the directory
# is deleted
gams.solve(
    m,
    keepfiles=False,
    logfile="gams_logfile.txt",
    tmpdir="./example_dir",
)

# logfile written to specified
# temporary working directory, along with other
# output files. path to the specified directory
# is displayed in console output
gams.solve(
    m,
    keepfiles=True,
    logfile="gams_logfile.txt",
    tmpdir="./example_dir",
)
```

### Information on your system

Pyomo version: 6.4.3dev0
Python version: 3.9.13
Operating system: Ubuntu 20.04
How Pyomo was installed (PyPI, conda, source): source (main branch)
Solver (if applicable): GAMS


### Additional information

<!-- If you have any additional information, please list it here. -->


<!-- We encourage you to try, as much as possible, to reduce your problem to the minimal example that still reproduces the issue. That would help us a lot in fixing it quickly and effectively!

If you want to ask a question (how to use Pyomo, what it can do, etc.), please refer to our [online documentation](https://github.com/Pyomo/pyomo#getting-help) on ways to get help. -->




Discussion:

Resolving patch:
diff --git a/pyomo/solvers/plugins/solvers/GAMS.py b/pyomo/solvers/plugins/solvers/GAMS.py
--- a/pyomo/solvers/plugins/solvers/GAMS.py
+++ b/pyomo/solvers/plugins/solvers/GAMS.py
@@ -261,6 +261,12 @@ def solve(self, *args, **kwds):
 
         initial_time = time.time()
 
+        # Because GAMS changes the CWD when running the solver, we need
+        # to convert user-provided file names to absolute paths
+        # (relative to the current directory)
+        if logfile is not None:
+            logfile = os.path.abspath(logfile)
+
         ####################################################################
         # Presolve
         ####################################################################
@@ -735,6 +741,12 @@ def solve(self, *args, **kwds):
         # any unrecognized arguments
         initial_time = time.time()
 
+        # Because GAMS changes the CWD when running the solver, we need
+        # to convert user-provided file names to absolute paths
+        # (relative to the current directory)
+        if logfile is not None:
+            logfile = os.path.abspath(logfile)
+
         ####################################################################
         # Presolve
         ####################################################################


Test patch:
diff --git a/pyomo/solvers/tests/checks/test_GAMS.py b/pyomo/solvers/tests/checks/test_GAMS.py
--- a/pyomo/solvers/tests/checks/test_GAMS.py
+++ b/pyomo/solvers/tests/checks/test_GAMS.py
@@ -17,6 +17,7 @@
     GAMSShell, GAMSDirect, gdxcc_available
 )
 import pyomo.common.unittest as unittest
+from pyomo.common.tempfiles import TempfileManager
 from pyomo.common.tee import capture_output
 import os, shutil
 from tempfile import mkdtemp
@@ -395,6 +396,23 @@ def test_logfile(self):
         self._check_stdout(output.getvalue(), exists=False)
         self._check_logfile(exists=True)
 
+    def test_logfile_relative(self):
+        cwd = os.getcwd()
+        with TempfileManager:
+            tmpdir = TempfileManager.create_tempdir()
+            os.chdir(tmpdir)
+            try:
+                self.logfile = 'test-gams.log'
+                with SolverFactory("gams", solver_io="gms") as opt:
+                    with capture_output() as output:
+                        opt.solve(self.m, logfile=self.logfile)
+                self._check_stdout(output.getvalue(), exists=False)
+                self._check_logfile(exists=True)
+                self.assertTrue(
+                    os.path.exists(os.path.join(tmpdir, self.logfile)))
+            finally:
+                os.chdir(cwd)
+
     def test_tee_and_logfile(self):
         with SolverFactory("gams", solver_io="gms") as opt:
             with capture_output() as output:
@@ -433,6 +451,22 @@ def test_logfile(self):
         self._check_stdout(output.getvalue(), exists=False)
         self._check_logfile(exists=True)
 
+    def test_logfile_relative(self):
+        cwd = os.getcwd()
+        with TempfileManager:
+            tmpdir = TempfileManager.create_tempdir()
+            os.chdir(tmpdir)
+            try:
+                self.logfile = 'test-gams.log'
+                with SolverFactory("gams", solver_io="python") as opt:
+                    with capture_output() as output:
+                        opt.solve(self.m, logfile=self.logfile)
+                self._check_stdout(output.getvalue(), exists=False)
+                self._check_logfile(exists=True)
+                self.assertTrue(
+                    os.path.exists(os.path.join(tmpdir, self.logfile)))
+            finally:
+                os.chdir(cwd)
     def test_tee_and_logfile(self):
         with SolverFactory("gams", solver_io="python") as opt:
             with capture_output() as output: