diff --git a/pyomo/repn/plugins/gams_writer.py b/pyomo/repn/plugins/gams_writer.py index d5e88f5f313..377e9789d0a 100644 --- a/pyomo/repn/plugins/gams_writer.py +++ b/pyomo/repn/plugins/gams_writer.py @@ -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, @@ -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( @@ -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): @@ -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 @@ -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: @@ -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'}, diff --git a/pyomo/solvers/plugins/solvers/GAMS.py b/pyomo/solvers/plugins/solvers/GAMS.py index 6b306838d5a..31d9a447b1a 100644 --- a/pyomo/solvers/plugins/solvers/GAMS.py +++ b/pyomo/solvers/plugins/solvers/GAMS.py @@ -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") @@ -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 @@ -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: @@ -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. diff --git a/pyomo/solvers/tests/checks/test_GAMS.py b/pyomo/solvers/tests/checks/test_GAMS.py index b022365707d..f7fe0655e62 100644 --- a/pyomo/solvers/tests/checks/test_GAMS.py +++ b/pyomo/solvers/tests/checks/test_GAMS.py @@ -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 @@ -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)