1#  ___________________________________________________________________________
2#
3#  Pyomo: Python Optimization Modeling Objects
4#  Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC
5#  Under the terms of Contract DE-NA0003525 with National Technology and
6#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
7#  rights in this software.
8#  This software is distributed under the 3-clause BSD License.
9#  ___________________________________________________________________________
10
11__all__ = ['SolverResults']
12
13import math
14import sys
15import copy
16import json
17import logging
18import os.path
19
20from pyomo.common.dependencies import yaml, yaml_load_args, yaml_available
21import pyomo.opt
22from pyomo.opt.results.container import (undefined,
23                                         ignore,
24                                         ListContainer,
25                                         MapContainer)
26import pyomo.opt.results.solution
27from pyomo.opt.results.solution import default_print_options as dpo
28import pyomo.opt.results.problem
29import pyomo.opt.results.solver
30
31from io import StringIO
32
33logger = logging.getLogger(__name__)
34
35class SolverResults(MapContainer):
36
37    undefined = undefined
38    default_print_options = dpo
39
40    def __init__(self):
41        MapContainer.__init__(self)
42        self._sections = []
43        self._descriptions = {}
44        self.add('problem',
45                 ListContainer(pyomo.opt.results.problem.ProblemInformation),
46                 False,
47                 "Problem Information")
48        self.add('solver',
49                 ListContainer(pyomo.opt.results.solver.SolverInformation),
50                 False,
51                 "Solver Information")
52        self.add('solution',
53                 pyomo.opt.results.solution.SolutionSet(),
54                 False,
55                 "Solution Information")
56
57    def add(self, name, value, active, description):
58        self.declare(name, value=value, active=active)
59        tmp = self._convert(name)
60        self._sections.append(tmp)
61        self._descriptions[tmp]=description
62
63    def json_repn(self, options=None):
64        if options is None:
65            return self._repn_(SolverResults.default_print_options)
66        else:
67            return self._repn_(options)
68
69    def _repn_(self, option):
70        if not option.schema and not self._active and not self._required:
71            return ignore
72        tmp = {}
73        for key in self._sections:
74            rep = dict.__getitem__(self, key)._repn_(option)
75            if not rep == ignore:
76                tmp[key] = rep
77        return tmp
78
79    def write(self, **kwds):
80        _fmt = kwds.pop('format', None)
81        if _fmt:
82            _fmt = _fmt.lower()
83        fname = kwds.pop('filename', None)
84
85        if fname:
86            ext = os.path.splitext(fname)[1].lstrip('.')
87            normalized_ext = {
88                'json': 'json',
89                'jsn': 'json',
90                'yaml': 'yaml',
91                'yml': 'yaml',
92            }.get(ext, None)
93            if not _fmt:
94                _fmt = normalized_ext
95            elif normalized_ext and _fmt != normalized_ext:
96                logger.warning(
97                    "writing results to file (%s) using what appears "
98                    "to be an incompatible format (%s)" % (fname, _fmt))
99            with open(fname, "w") as OUTPUT:
100                kwds['ostream'] = OUTPUT
101                kwds['format'] = _fmt
102                self.write(**kwds)
103        else:
104            if not _fmt:
105                _fmt = 'yaml'
106            if _fmt == 'yaml':
107                self.write_yaml(**kwds)
108            elif _fmt == 'json':
109                self.write_json(**kwds)
110            else:
111                raise ValueError("Unknown results file format: %s" % (_fmt,))
112
113    def write_json(self, **kwds):
114        if 'ostream' in kwds:
115            ostream = kwds['ostream']
116            del kwds['ostream']
117        else:
118            ostream = sys.stdout
119
120        option = copy.copy(SolverResults.default_print_options)
121        # TODO: verify that we need this for-loop
122        for key in kwds:
123            setattr(option,key,kwds[key])
124        repn = self.json_repn(option)
125
126        for soln in repn.get('Solution', []):
127            for data in ['Variable', 'Constraint', 'Objective']:
128                remove = set()
129                if data not in soln:
130                    continue
131                data_value = soln[data]
132                if not isinstance(data_value,dict):
133                    continue
134                if not data_value:
135                    # a variable/constraint/objective may have no
136                    # entries, e.g., if duals or slacks weren't
137                    # extracted in a solution.
138                    soln[data] = "No values"
139                    continue
140                for kk,vv in data_value.items():
141                    # TODO: remove this if-block.  This is a hack
142                    if not type(vv) is dict:
143                        vv = {'Value':vv}
144                    tmp = {}
145                    for k,v in vv.items():
146                        # TODO: remove this if-block.  This is a hack
147                        if v is not None and math.fabs(v) > 1e-16:
148                            tmp[k] = v
149                    if len(tmp) > 0:
150                        soln[data][kk] = tmp
151                    else:
152                        remove.add((data,kk))
153                for item in remove:
154                    del soln[item[0]][item[1]]
155        json.dump(repn, ostream, indent=4, sort_keys=True)
156
157    def write_yaml(self, **kwds):
158        if 'ostream' in kwds:
159            ostream = kwds['ostream']
160            del kwds['ostream']
161        else:
162            ostream = sys.stdout
163
164        option = copy.copy(SolverResults.default_print_options)
165        # TODO: verify that we need this for-loop
166        for key in kwds:
167            setattr(option,key,kwds[key])
168        repn = self._repn_(option)
169
170        ostream.write("# ==========================================================\n")
171        ostream.write("# = Solver Results                                         =\n")
172        ostream.write("# ==========================================================\n")
173        for i in range(len(self._order)):
174            key = self._order[i]
175            if not key in repn:
176                continue
177            item = dict.__getitem__(self,key)
178            ostream.write("# ----------------------------------------------------------\n")
179            ostream.write("#   %s\n" % self._descriptions[key])
180            ostream.write("# ----------------------------------------------------------\n")
181            ostream.write(key+": ")
182            if isinstance(item, ListContainer):
183                item.pprint(ostream, option, prefix="", repn=repn[key])
184            else:
185                item.pprint(ostream, option, prefix="  ", repn=repn[key])
186
187    def read(self, **kwds):
188        if 'istream' in kwds:
189            istream = kwds['istream']
190            del kwds['istream']
191        else:
192            ostream = sys.stdin
193        if 'filename' in kwds:
194            INPUT=open(kwds['filename'],"r")
195            del kwds['filename']
196            kwds['istream']=INPUT
197            self.read(**kwds)
198            INPUT.close()
199            return
200
201        if not 'format' in kwds or kwds['format'] == 'yaml':
202            repn = yaml.load(istream, **yaml_load_args)
203        else:
204            repn = json.load(istream)
205        for i in range(len(self._order)):
206            key = self._order[i]
207            if not key in repn:
208                continue
209            item = dict.__getitem__(self,key)
210            item.load(repn[key])
211
212    def __repr__(self):
213        return str(self._repn_(SolverResults.default_print_options))
214
215    def __str__(self):
216        ostream = StringIO()
217        option=SolverResults.default_print_options
218        self.pprint(ostream, option, repn=self._repn_(option))
219        return ostream.getvalue()
220
221
222if __name__ == '__main__':
223    results = SolverResults()
224    results.write(schema=True)
225    #print results
226