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

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

Update to Coopr to account for changes in PyUtilib? package names.

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