Skip to content

Commit

Permalink
Merge pull request #1 from jsiirola/gams_gdx
Browse files Browse the repository at this point in the history
Support PUT and GDX GAMS interfaces
  • Loading branch information
renkekuhlmann committed May 20, 2020
2 parents 4abffb4 + 8163bbd commit 6121dde
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 99 deletions.
55 changes: 45 additions & 10 deletions pyomo/repn/plugins/gams_writer.py
Expand Up @@ -322,8 +322,16 @@ def __call__(self,
| 2 : sort keys AND sort names (over declaration order)
- put_results=None
Filename for optionally writing solution values and
marginals to (put_results).dat, and solver statuses
to (put_results + 'stat').dat.
marginals. If put_results_format is 'gdx', then GAMS
will write solution values and marginals to
GAMS_MODEL_p.gdx and solver statuses to
{put_results}_s.gdx. If put_results_format is 'dat',
then solution values and marginals are written to
(put_results).dat, and solver statuses to (put_results +
'stat').dat.
- put_results_format='gdx'
Format used for put_results, one of 'gdx', 'dat'.
"""

# Make sure not to modify the user's dictionary,
Expand Down Expand Up @@ -374,6 +382,8 @@ def __call__(self,
# Filename for optionally writing solution values and marginals
# Set to True by GAMSSolver
put_results = io_options.pop("put_results", None)
put_results_format = io_options.pop("put_results_format", 'gdx')
assert put_results_format in ('gdx','dat')

if len(io_options):
raise ValueError(
Expand Down Expand Up @@ -472,7 +482,8 @@ def var_label(obj):
limcol=limcol,
solvelink=solvelink,
add_options=add_options,
put_results=put_results
put_results=put_results,
put_results_format=put_results_format,
)
finally:
if isinstance(output_filename, string_types):
Expand All @@ -498,7 +509,9 @@ def _write_model(self,
limcol,
solvelink,
add_options,
put_results):
put_results,
put_results_format,
):
constraint_names = []
ConstraintIO = StringIO()
linear = True
Expand Down Expand Up @@ -699,7 +712,7 @@ def _write_model(self,
output_file.write("option limcol=%d;\n" % limcol)
output_file.write("option solvelink=%d;\n" % solvelink)

if put_results is not None:
if put_results is not None and put_results_format == 'gdx':
output_file.write("option savepoint=1;\n")

if add_options is not None:
Expand Down Expand Up @@ -743,11 +756,33 @@ def _write_model(self,
output_file.write("ETSOLVE = %s.etsolve\n\n" % model_name)

if put_results is not None:
output_file.write("\nexecute_unload '%s_s.gdx'" % model_name)
for stat in stat_vars:
output_file.write(", %s" % stat)
output_file.write(";\n")

if put_results_format == 'gdx':
output_file.write("\nexecute_unload '%s_s.gdx'" % put_results)
for stat in stat_vars:
output_file.write(", %s" % stat)
output_file.write(";\n")
else:
results = put_results + '.dat'
output_file.write("\nfile results /'%s'/;" % results)
output_file.write("\nresults.nd=15;")
output_file.write("\nresults.nw=21;")
output_file.write("\nput results;")
output_file.write("\nput 'SYMBOL : LEVEL : MARGINAL' /;")
for var in var_list:
output_file.write("\nput %s %s.l %s.m /;" % (var, var, var))
for con in constraint_names:
output_file.write("\nput %s %s.l %s.m /;" % (con, con, con))
output_file.write("\nput GAMS_OBJECTIVE GAMS_OBJECTIVE.l "
"GAMS_OBJECTIVE.m;\n")

statresults = put_results + 'stat.dat'
output_file.write("\nfile statresults /'%s'/;" % statresults)
output_file.write("\nstatresults.nd=15;")
output_file.write("\nstatresults.nw=21;")
output_file.write("\nput statresults;")
output_file.write("\nput 'SYMBOL : VALUE' /;")
for stat in stat_vars:
output_file.write("\nput '%s' %s /;\n" % (stat, stat))

valid_solvers = {
'ALPHAECP': {'MINLP','MIQCP'},
Expand Down
204 changes: 120 additions & 84 deletions pyomo/solvers/plugins/solvers/GAMS.py
Expand Up @@ -585,15 +585,7 @@ def available(self, exception_flag=True):
raise NameError(
"No 'gams' command found on system PATH - GAMS shell "
"solver functionality is not available.")

if gdxcc_available:
return True
elif exception_flag:
raise ImportError("Import of gams failed - GAMS direct "
"solver functionality is not available.\n"
"GAMS message: %s" % (e,))
else:
return False
return True

def _default_executable(self):
executable = pyomo.common.Executable("gams")
Expand Down Expand Up @@ -716,8 +708,19 @@ def solve(self, *args, **kwds):

put_results = "results"
io_options["put_results"] = put_results
results_filename = os.path.join(tmpdir, "GAMS_MODEL_p.gdx")
statresults_filename = os.path.join(tmpdir, "GAMS_MODEL_s.gdx")
io_options.setdefault("put_results_format",
'gdx' if gdxcc_available else 'dat')

if io_options['put_results_format'] == 'gdx':
results_filename = os.path.join(
tmpdir, "GAMS_MODEL_p.gdx")
statresults_filename = os.path.join(
tmpdir, "%s_s.gdx" % (put_results,))
else:
results_filename = os.path.join(
tmpdir, "%s.dat" % (put_results,))
statresults_filename = os.path.join(
tmpdir, "%sstat.dat" % (put_results,))

if isinstance(model, IBlock):
# Kernel blocks have slightly different write method
Expand Down Expand Up @@ -781,79 +784,12 @@ def solve(self, *args, **kwds):
raise RuntimeError("GAMS encountered an error during solve. "
"Check listing file for details.")

model_soln = dict()
stat_vars = dict.fromkeys(['MODELSTAT', 'SOLVESTAT', 'OBJEST',
'OBJVAL', 'NUMVAR', 'NUMEQU', 'NUMDVAR',
'NUMNZ', 'ETSOLVE'])

pgdx = gdxcc.new_gdxHandle_tp()
ret = gdxcc.gdxCreateD(pgdx, os.path.dirname(self.executable()), 128)
if not ret[0]:
raise RuntimeError("GAMS GDX failure (gdxCreate): %s." % ret[1])

if os.path.exists(statresults_filename):
ret = gdxcc.gdxOpenRead(pgdx, statresults_filename)
if not ret[0]:
raise RuntimeError("GAMS GDX failure (gdxOpenRead): %d." % ret[1])

i = 0
while True:
i += 1
ret = gdxcc.gdxDataReadRawStart(pgdx, i)
if not ret[0]:
break

ret = gdxcc.gdxSymbolInfo(pgdx, i)
if not ret[0]:
break
if len(ret) < 2:
raise RuntimeError("GAMS GDX failure (gdxSymbolInfo).")
stat = ret[1]
if not stat in stat_vars:
continue

ret = gdxcc.gdxDataReadRaw(pgdx)
if not ret[0] or len(ret[2]) == 0:
raise RuntimeError("GAMS GDX failure (gdxDataReadRaw).")

if stat in ('OBJEST', 'OBJVAL', 'ETSOLVE'):
stat_vars[stat] = self._parse_special_values(ret[2][0])
else:
stat_vars[stat] = int(ret[2][0])

gdxcc.gdxDataReadDone(pgdx)
gdxcc.gdxClose(pgdx)

if os.path.exists(results_filename):
ret = gdxcc.gdxOpenRead(pgdx, results_filename)
if not ret[0]:
raise RuntimeError("GAMS GDX failure (gdxOpenRead): %d." % ret[1])

i = 0
while True:
i += 1
ret = gdxcc.gdxDataReadRawStart(pgdx, i)
if not ret[0]:
break

ret = gdxcc.gdxDataReadRaw(pgdx)
if not ret[0] or len(ret[2]) < 2:
raise RuntimeError("GAMS GDX failure (gdxDataReadRaw).")
level = self._parse_special_values(ret[2][0])
dual = self._parse_special_values(ret[2][1])

ret = gdxcc.gdxSymbolInfo(pgdx, i)
if not ret[0]:
break
if len(ret) < 2:
raise RuntimeError("GAMS GDX failure (gdxSymbolInfo).")
model_soln[ret[1]] = (level, dual)

gdxcc.gdxDataReadDone(pgdx)
gdxcc.gdxClose(pgdx)

gdxcc.gdxFree(pgdx)

if io_options['put_results_format'] == 'gdx':
model_soln, stat_vars = self._parse_gdx_results(
results_filename, statresults_filename)
else:
model_soln, stat_vars = self._parse_dat_results(
results_filename, statresults_filename)
finally:
if not keepfiles:
if newdir:
Expand Down Expand Up @@ -1138,6 +1074,106 @@ def solve(self, *args, **kwds):

return results

def _parse_gdx_results(self, results_filename, statresults_filename):
model_soln = dict()
stat_vars = dict.fromkeys(['MODELSTAT', 'SOLVESTAT', 'OBJEST',
'OBJVAL', 'NUMVAR', 'NUMEQU', 'NUMDVAR',
'NUMNZ', 'ETSOLVE'])

pgdx = gdxcc.new_gdxHandle_tp()
ret = gdxcc.gdxCreateD(pgdx, os.path.dirname(self.executable()), 128)
if not ret[0]:
raise RuntimeError("GAMS GDX failure (gdxCreate): %s." % ret[1])

if os.path.exists(statresults_filename):
ret = gdxcc.gdxOpenRead(pgdx, statresults_filename)
if not ret[0]:
raise RuntimeError("GAMS GDX failure (gdxOpenRead): %d." % ret[1])

i = 0
while True:
i += 1
ret = gdxcc.gdxDataReadRawStart(pgdx, i)
if not ret[0]:
break

ret = gdxcc.gdxSymbolInfo(pgdx, i)
if not ret[0]:
break
if len(ret) < 2:
raise RuntimeError("GAMS GDX failure (gdxSymbolInfo).")
stat = ret[1]
if not stat in stat_vars:
continue

ret = gdxcc.gdxDataReadRaw(pgdx)
if not ret[0] or len(ret[2]) == 0:
raise RuntimeError("GAMS GDX failure (gdxDataReadRaw).")

if stat in ('OBJEST', 'OBJVAL', 'ETSOLVE'):
stat_vars[stat] = self._parse_special_values(ret[2][0])
else:
stat_vars[stat] = int(ret[2][0])

gdxcc.gdxDataReadDone(pgdx)
gdxcc.gdxClose(pgdx)

if os.path.exists(results_filename):
ret = gdxcc.gdxOpenRead(pgdx, results_filename)
if not ret[0]:
raise RuntimeError("GAMS GDX failure (gdxOpenRead): %d." % ret[1])

i = 0
while True:
i += 1
ret = gdxcc.gdxDataReadRawStart(pgdx, i)
if not ret[0]:
break

ret = gdxcc.gdxDataReadRaw(pgdx)
if not ret[0] or len(ret[2]) < 2:
raise RuntimeError("GAMS GDX failure (gdxDataReadRaw).")
level = self._parse_special_values(ret[2][0])
dual = self._parse_special_values(ret[2][1])

ret = gdxcc.gdxSymbolInfo(pgdx, i)
if not ret[0]:
break
if len(ret) < 2:
raise RuntimeError("GAMS GDX failure (gdxSymbolInfo).")
model_soln[ret[1]] = (level, dual)

gdxcc.gdxDataReadDone(pgdx)
gdxcc.gdxClose(pgdx)

gdxcc.gdxFree(pgdx)
return model_soln, stat_vars

def _parse_dat_results(self, results_filename, statresults_filename):
with open(statresults_filename, 'r') as statresults_file:
statresults_text = statresults_file.read()

stat_vars = dict()
# Skip first line of explanatory text
for line in statresults_text.splitlines()[1:]:
items = line.split()
try:
stat_vars[items[0]] = float(items[1])
except ValueError:
# GAMS printed NA, just make it nan
stat_vars[items[0]] = float('nan')

with open(results_filename, 'r') as results_file:
results_text = results_file.read()

model_soln = dict()
# Skip first line of explanatory text
for line in results_text.splitlines()[1:]:
items = line.split()
model_soln[items[0]] = (items[1], items[2])

return model_soln, stat_vars


class OutputStream:
"""Output stream object for simultaneously writing to multiple streams.
Expand Down
18 changes: 13 additions & 5 deletions pyomo/solvers/tests/checks/test_GAMS.py
Expand Up @@ -10,7 +10,9 @@


from pyomo.environ import *
from pyomo.solvers.plugins.solvers.GAMS import GAMSShell, GAMSDirect
from pyomo.solvers.plugins.solvers.GAMS import (
GAMSShell, GAMSDirect, gdxcc_available
)
import pyutilib.th as unittest
from pyutilib.misc import capture_output
import os, shutil
Expand Down Expand Up @@ -156,10 +158,16 @@ def test_keepfiles_gms(self):
'model.gms')))
self.assertTrue(os.path.exists(os.path.join(tmpdir,
'output.lst')))
self.assertTrue(os.path.exists(os.path.join(tmpdir,
'GAMS_MODEL_p.gdx')))
self.assertTrue(os.path.exists(os.path.join(tmpdir,
'GAMS_MODEL_s.gdx')))
if gdxcc_available:
self.assertTrue(os.path.exists(os.path.join(
tmpdir, 'GAMS_MODEL_p.gdx')))
self.assertTrue(os.path.exists(os.path.join(
tmpdir, 'results_s.gdx')))
else:
self.assertTrue(os.path.exists(os.path.join(
tmpdir, 'results.dat')))
self.assertTrue(os.path.exists(os.path.join(
tmpdir, 'resultsstat.dat')))

shutil.rmtree(tmpdir)

Expand Down

0 comments on commit 6121dde

Please sign in to comment.