source: coopr.pyomo/trunk/coopr/pyomo/base/PyomoModel.py @ 2053

Last change on this file since 2053 was 2053, checked in by wehart, 10 years ago

Adding ability to load a list or tuple of values into a
Pyomo model. These are loaded into the variables in the
order in which the variables are identified.

  • Property svn:executable set to *
File size: 17.9 KB
Line 
1#  _________________________________________________________________________
2#
3#  Coopr: A COmmon Optimization Python Repository
4#  Copyright (c) 2008 Sandia Corporation.
5#  This software is distributed under the BSD License.
6#  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
7#  the U.S. Government retains certain rights in this software.
8#  For more information, see the Coopr README.txt file.
9#  _________________________________________________________________________
10
11import copy
12import re
13import sys
14
15from plugin import *
16from numvalue import *
17
18import pyutilib.plugin.core
19from pyutilib.math import *
20from pyutilib.misc import quote_split, tuplize
21
22from coopr.opt import ProblemFormat, ResultsFormat, guess_format
23from coopr.pyomo.base.var import _VarValue
24import coopr.opt
25from PyomoModelData import ModelData
26import pyomo
27
28from component import Component
29from sets import Set, _ProductSet, _SetContainer, _BaseSet
30from rangeset import RangeSet
31from var import Var
32from constraint import Objective, Constraint, ConstraintData
33from param import Param
34
35
36class Model(Component):
37    """
38    The MINLP model that will be analyzed by the user.
39    """
40
41    presolver_ep = pyutilib.plugin.core.ExtensionPoint(IPyomoPresolver)
42
43
44    def __init__(self):
45        """Constructor"""
46        Component.__init__(self, ctype=Model)
47        #
48        # Define component dictionary: component type -> instance
49        #
50        self._component={}
51        for item in pyutilib.plugin.core.ExtensionPoint(IModelComponent):
52            self._component[ item.cls() ] = {}
53        #
54        # A list of the declarations, in the order that they are
55        # specified.
56        #
57        self._declarations=[]
58        # the _name_varmap and _name_conmap dictionarys are assigned when
59        # the problem definition
60        # file is written, i.e., via the write() command. intent
61        # is to map names that will be coming back from a solver
62        # into Pyomo variables. Values are of type _VarValue.
63        self._name_varmap={}
64        self._name_conmap={}
65        self._name_objmap={}
66        self.name="unknown"
67        self.tmpctr=0
68        self._varctr=0
69        self._presolvers = ["simple_presolver"]
70        #
71        # Dictionaries that map from id to Variable/Constraints
72        #
73        self._var = {}
74        self._con = {}
75        self._obj = {}
76
77    def components(self, ctype=None):
78        if ctype is None:
79            return self._component
80        if ctype in self._component:
81             return self._component[ctype]
82        raise KeyError, "Unknown component type: %s" % str(ctype)
83
84    def nvariables(self):
85        return len(self._var)
86
87    def variable(self, name):
88        if isinstance(name,basestring):
89            return self._name_varmap[name]
90        else:
91            return self._var[name]
92
93    def variables(self):
94        return self._name_varmap
95
96    def nconstraints(self):
97        return len(self._con)
98
99    def constraint(self, name):
100        if isinstance(name,basestring):
101            return self._name_conmap[name]
102        else:
103            return self._con[name]
104
105    def constraints(self):
106        return self._name_conmap
107
108    def nobjectives(self):
109        return len(self._obj)
110
111    def objective(self, name):
112        if isinstance(name,basestring):
113            return self._name_objmap[name]
114        else:
115            return self._obj[name]
116
117    def objectives(self):
118        return self._name_objmap
119
120
121    def active_components(self, _ctype=None):
122        tmp = {}
123        if _ctype is None:
124            for ctype in self._component:
125                tmp[ctype]={}
126        elif _ctype in self._component:
127            tmp[_ctype]={}
128        else:
129            raise KeyError, "Unknown component type: %s" % str(ctype)
130        for ctype in tmp:
131            for name in self._component[ctype]:
132                comp = self._component[ctype][name]
133                if comp.active:
134                    tmp[ctype][name] = comp
135        if not _ctype is None:
136            return tmp[_ctype]
137        print "ACTIVE",tmp
138        return tmp
139
140    def num_used_variables(self):
141        return len(self._var)
142
143
144    def valid_problem_types(self):
145        return [ProblemFormat.pyomo]
146
147
148    def _add_temporary_set(self,val):
149        if val._index_set is not None:
150            ctr=0
151            for tset in val._index_set:
152                if tset.name == "_unknown_":
153                    self._construct_temporary_set(tset,val.name+"_index_"+str(ctr))
154                ctr+=1
155            val._index = self._construct_temporary_set(val._index_set,val.name+"_index")
156        if isinstance(val._index,_SetContainer) and \
157            val._index.name == "_unknown_":
158            self._construct_temporary_set(val._index,val.name+"_index")
159        if val.domain is not None and val.domain.name == "_unknown_":
160            self._construct_temporary_set(val.domain,val.name+"_domain")
161
162
163    def _construct_temporary_set(self, obj, name):
164        if type(obj) is tuple:
165           if len(obj) == 1:                #pragma:nocover
166                  raise Exception, "Unexpected temporary set construction"
167           else:
168                  tobj=_ProductSet(*obj)
169                  setattr(self,name,tobj)
170                  tobj.virtual=True
171                  return tobj
172        if isinstance(obj,_BaseSet):
173           setattr(self,name,obj)
174           return obj
175
176
177    def _clear_attribute(self,name):
178        #
179        # Cleanup Old Model Declaration First
180        #
181        i=0
182        for decl in self._declarations:
183            if decl.name == name:
184                self.__dict__[name]=None
185                del self._declarations[i]
186                for item in self._component:
187                    if name in self._component[item]:
188                        del self._component[item][name]
189                        break
190                break
191            i += 1
192
193    def _setattr_exec(self,name,val):
194        self._clear_attribute(name)
195        val.name=name
196        self._add_temporary_set(val)
197        self._component[val.type()][name]=val
198        self._declarations.append(val)
199        self.__dict__[name]=val
200        if not val.type() is Model:
201            val.model=self
202
203    def __setattr__(self,name,val):
204        """Set attributes"""
205        #
206        # Set Model Declaration
207        #
208        if name != "_component":
209            #
210            # If this is a component type, then simply set it
211            #
212            if isinstance(val,Component):
213                self._setattr_exec(name,val)
214                return
215        #
216        # Try to set the value.  This may fail if the attribute does not already
217        # exist in this model, if the set_value function is not defined, or if
218        # a bad value is provided.  In the latter case, a ValueError will be thrown, which
219        # we raise.  Otherwise, this is an object that we need to set directly.
220        #
221        try:
222            self.__dict__[name].set_value(val)
223        except ValueError, e:
224            raise
225        except Exception, e:
226            self.__dict__[name]=val
227
228
229    def create(self, filename=None, name=None):
230        """
231        Create a concrete instance of this Model, possibly using data
232        read in from a file.
233        """
234        instance = self.clone()
235        instance.load(filename)
236        instance.presolve()
237        if not name is None:
238            instance.name=name
239        return instance
240
241
242    def clone(self):
243        """Create a copy of this model"""
244        for declaration in self._declarations:
245          declaration.model = None
246        instance = copy.deepcopy(self)
247        for declaration in self._declarations:
248          declaration.model = self
249        for declaration in instance._declarations:
250          declaration.model = instance
251        return instance
252
253
254    def reset(self):
255        for declaration in self._declarations:
256            declaration.reset()
257
258
259    def presolve(self):
260        """Apply the presolvers defined by the user"""
261        for item in self._presolvers:
262            self = Model.presolver_ep.service(item).presolve(self)
263       
264
265    def solution(self):
266        """Return the solution"""
267        soln = []
268        for i in range(len(self._var)):
269            soln.append( self._var[i].value )
270        return soln
271
272
273    def load(self, arg):
274        """ Load the model with data from a file or a Solution object """
275        if arg is None or type(arg) is str:
276           self._load_model_data(ModelData(filename=arg,model=self))
277           return True
278        elif type(arg) is ModelData:
279           self._load_model_data(arg)
280           return True
281        elif type(arg) is coopr.opt.SolverResults:
282           if arg.solver.status != coopr.opt.SolverStatus.ok:
283              raise ValueError, "Cannot load a SolverResults object with bad status: "+str(arg.solver.status)
284           if len(arg.solution) > 0:
285              self._load_solution(arg.solution(0),symbol_map=arg.symbol_map)
286              return True
287           else:
288              return False
289        elif type(arg) is coopr.opt.Solution:
290           self._load_solution(arg)
291           return True
292        elif type(arg) in (tuple, list):
293           self._load_solution(arg)
294           return True
295        else:
296           raise ValueError, "Cannot load model with object of type "+str(type(arg))
297
298
299    def _tuplize(self, data, setobj):
300        if data is None:            #pragma:nocover
301           return None
302        if setobj.dimen == 1:
303           return data
304        ans = {}
305        for key in data:
306          if type(data[key][0]) is tuple:
307             return data
308          ans[key] = tuplize(data[key], setobj.dimen, setobj.name)
309        return ans
310
311
312    def _load_model_data(self,modeldata):
313        """
314        Load declarations from a ModelData object.
315        """
316        #print "HERE _load_model_data",self._declarations
317        for declaration in self._declarations:
318            if declaration.type() is Model:
319                continue
320            tmp = declaration.name
321            if tmp in modeldata._default.keys():
322                if declaration.type() is Set:
323                    declaration.default = self._tuplize(modeldata._default[tmp],declaration)
324                else:
325                    declaration.default = modeldata._default[tmp]
326            if tmp in modeldata._data.keys():
327                #print "HERE", declaration.name, str(declaration.dimen),modeldata._data[tmp]
328                if declaration.type() is Set:
329                    data = self._tuplize(modeldata._data[tmp],declaration)
330                else:
331                    data = modeldata._data[tmp]
332            else:
333                data = None
334            if pyomo.debug("generate"):           #pragma:nocover
335                print "About to generate '"+declaration.name+"' with data: "+str(data)
336                self.pprint()
337            declaration.construct(data)
338            try:
339                pass
340            except Exception, err:
341                print "Error constructing declaration "+str(declaration.name)+" from data="+str(data)
342                print "ERROR: "+str(err)
343                raise err
344
345    def _load_solution(self, soln, symbol_map=None):
346        """
347        Load a solution.
348        """
349        # Load list or tuple into the variables, based on their order
350        if type(soln) in (list, tuple):
351            if len(soln) != len(self._var):
352                raise ValueError, "Attempting to load a list/tuple solution into a model, but its length is %d while the model has %d variables" % (len(soln), len(self._var))
353            for i in range(len(soln)):
354                self._var[i].value = soln[i]
355            return
356        #
357        # Load variable data
358        #
359        for name in soln.variable:
360          #
361          # NOTE: the following is a hack, to handle the ONE_VAR_CONSTANT variable
362          #       that is necessary for the objective constant-offset terms.
363          #       probably should create a dummy variable in the model map at the same
364          #       time the objective expression is being constructed.
365          #
366          if name != "ONE_VAR_CONSTANT":
367             # translate the name first if there is a variable map associated with the input solution.
368             if symbol_map is not None:
369                 name = symbol_map[name]
370             if name not in self._name_varmap:
371                names=""
372                raise KeyError, "Variable name '"+name+"' is not in model "+self.name+".  Valid variable names: "+str(self._name_varmap.keys())
373             if not isinstance(self._name_varmap[name],_VarValue):
374                raise TypeError, "Variable '"+name+"' in model '"+self.name+"' is type "+str(type(self._name_varmap[name]))
375             if self._name_varmap[name].fixed is True:
376                raise TypeError, "Variable '"+name+"' in model '"+self.name+"' is currently fixed - new value is not expected in solution"
377
378             #print "HERE",soln.variable[name].keys()
379             for _key in soln.variable[name].keys():
380                key = _key[0].lower() + _key[1:] 
381                if key == 'value':
382                    self._name_varmap[name].value = soln.variable[name].value
383                elif not key == 'id':
384                    setattr(self._name_varmap[name], key, getattr(soln.variable[name], _key))
385        #
386        # Load constraint data
387        #
388        for name in soln.constraint:
389             #
390             # This is a hack
391             #
392             if name.endswith('ONE_VAR_CONSTANT'):
393                continue
394             if symbol_map is not None:
395                name = symbol_map[name]
396             #
397             # This is a hack.  Is there a standard convention for constraint
398             # names that we can apply to all data formats?
399             #
400             if name[0:2] == 'c_':
401                _name = name[4:-1]
402             #print "HERE",_name
403             if _name not in self._name_conmap:
404                names=""
405                raise KeyError, "Constraint name '"+name+"' is not in model "+self.name+".  Valid constraint names: "+str(self._name_conmap.keys())
406             if not isinstance(self._name_conmap[_name],ConstraintData):
407                raise TypeError, "Constraint '"+name+"' in model '"+self.name+"' is type "+str(type(self._name_conmap[_name]))
408
409             #print "HERE",soln.constraint[name].keys()
410             for _key in soln.constraint[name].keys():
411                key = _key[0].lower() + _key[1:] 
412                if key == 'value':
413                    self._name_conmap[name].value = soln.constraint[name].value
414                elif not key == 'id':
415                    setattr(self._name_conmap[_name], key, getattr(soln.constraint[name], _key))
416
417    def write(self,filename=None,format=ProblemFormat.cpxlp):
418        """
419        Write the model to a file, with a given format.
420        """
421        if format is None and not filename is None:
422            #
423            # Guess the format
424            #
425            format = guess_format(filename)
426        problem_writer = coopr.opt.WriterFactory(format)
427        if problem_writer is None:
428           raise ValueError, "Cannot write model in format \"" + str(format) + "\": no model writer registered for that format"
429        (fname, symbol_map) = problem_writer(self, filename)
430        if pyomo.debug("verbose"):
431           print "Writing model "+self.name+" to file '"+str(fname)+"'  with format "+str(format)
432        return fname, symbol_map
433
434
435    def pprint(self, filename=None, ostream=None):
436        """
437        Print a summary of the model info
438        """
439        if ostream is None:
440           ostream = sys.stdout
441        if filename is not None:
442           OUTPUT=open(filename,"w")
443           self.pprint(ostream=OUTPUT)
444           OUTPUT.close()
445           return
446        if ostream is None:
447           ostream = sys.stdout
448        #
449        # We hard-code the order of the core Pyomo modeling
450        # components, to ensure that the output follows the logical
451        # expected by a user.
452        #
453        items = [Set, RangeSet, Param, Var, Objective, Constraint]
454        for item in pyutilib.plugin.core.ExtensionPoint(IModelComponent):
455            if not item in items:
456                items.append(item)
457        for item in items:
458            if not item in self._component:
459                continue
460            keys = self._component[item].keys()
461            keys.sort()
462            #
463            # NOTE: these conditional checks should not be hard-coded.
464            #
465            print >>ostream, len(keys), item.__name__+" Declarations"
466            for key in keys:
467                self._component[item][key].pprint(ostream)
468            print >>ostream, ""
469        #
470        # Model Order
471        #
472        print >>ostream, len(self._declarations),"Declarations:",
473        for i in range(0,len(self._declarations)):
474          print >>ostream, self._declarations[i].name,
475        print >>ostream, ""
476
477
478    def display(self, filename=None, ostream=None):
479        """
480        Print the Pyomo model in a verbose format.
481        """
482        if filename is not None:
483           OUTPUT=open(filename,"w")
484           self.display(ostream=OUTPUT)
485           OUTPUT.close()
486           return
487        if ostream is None:
488           ostream = sys.stdout
489        print >>ostream, "Model "+self.name
490        print >>ostream, ""
491        print >>ostream, "  Variables:"
492        VAR = self.active_components(Var)
493        if len(VAR) == 0:
494            print >>ostream, "    None"
495        else:
496            for ndx in VAR:
497                VAR[ndx].display(prefix="    ",ostream=ostream)
498        print >>ostream, ""
499        print >>ostream, "  Objectives:"
500        OBJ = self.active_components(Objective)
501        if len(OBJ) == 0:
502            print >>ostream, "    None"
503        else:
504            for ndx in OBJ:
505                OBJ[ndx].display(prefix="    ",ostream=ostream)
506        print >>ostream, ""
507        CON = self.active_components(Constraint)
508        print >>ostream, "  Constraints:"
509        if len(CON) == 0:
510            print >>ostream, "    None"
511        else:
512            for ndx in CON:
513                CON[ndx].display(prefix="    ",ostream=ostream)
514
515ComponentRegistration("Model", Model, "Model objects can be used as a component of other models.")
Note: See TracBrowser for help on using the repository browser.