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

Last change on this file since 2415 was 2415, checked in by jwatson, 9 years ago

Significantly improved performance of PyomoModel? _clear_attribute method, mainly by eliminate unnecessary calls to it through _setattr_exec_.

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