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

Last change on this file since 2359 was 2359, checked in by wehart, 9 years ago
  1. Renaming the 'presolve' phase in Pyomo to 'preprocess'. We'll

eventually want an explicit presolve phase, but that will be applied
as a transformation, right before the problem is written and sent
to the solver. Currently, this preprocess phase collects data about
the model and computes the canonical representations for expressions.

  1. Created the ability to support the construction of concrete models.

The concrete_mode() method for Model objects enables this mode.
When in the concrete mode, each component added to the model has
its construct() method called immediately after the component is
added to the model. The symbolic_mode() method for Model objects
can be called to switch back to a symbolic mode (which defers the
execution of construct().

Bad things might happen if you switch back and forth between these
modes. Notably, if you ever generate components in symbolic mode,
then you need to switch back to symbolic mode before calling create().

Note that the Model.create() method is still called when the model
construction is 'done', even when done completely in concrete mode.
This basically calls the preprocessing steps, and this method returns
the current model (i.e. no cloning is done when building a concrete
model).

  1. Created an example directory for concrete models. Note that these

can still be solved with the Pyomo command line. In this
instance, the command line simply executes the script and applies
a solver.

  • Property svn:executable set to *
File size: 19.4 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 Old Model Declaration First
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        self._clear_attribute(name)
205        val.name=name
206        self._add_temporary_set(val)
207        self._component[val.type()][name]=val
208        self._declarations.append(val)
209        self.__dict__[name]=val
210        if not val.type() is Model:
211            val.model=self
212        if not self._defer_construction:
213            val.construct(None)
214
215    def __setattr__(self,name,val):
216        """Set attributes"""
217        #
218        # Set Model Declaration
219        #
220        if name != "_component":
221            #
222            # If this is a component type, then simply set it
223            #
224            if isinstance(val,Component):
225                self._setattr_exec(name,val)
226                return
227        #
228        # Try to set the value.  This may fail if the attribute does not already
229        # exist in this model, if the set_value function is not defined, or if
230        # a bad value is provided.  In the latter case, a ValueError will be thrown, which
231        # we raise.  Otherwise, this is an object that we need to set directly.
232        #
233        try:
234            self.__dict__[name].set_value(val)
235        except ValueError, e:
236            raise
237        except Exception, e:
238            self.__dict__[name]=val
239
240
241    def create(self, filename=None, name=None):
242        """
243        Create a concrete instance of this Model, possibly using data
244        read in from a file.
245        """
246        if self._defer_construction:
247            instance = self.clone()
248            instance.load(filename)
249        else:
250            instance = self
251        instance.preprocess()
252        if not name is None:
253            instance.name=name
254        return instance
255
256
257    def clone(self):
258        """Create a copy of this model"""
259        for declaration in self._declarations:
260          declaration.model = None
261        instance = copy.deepcopy(self)
262        for declaration in self._declarations:
263          declaration.model = self
264        for declaration in instance._declarations:
265          declaration.model = instance
266        return instance
267
268
269    def reset(self):
270        for declaration in self._declarations:
271            declaration.reset()
272
273
274    def preprocess(self):
275        """Apply the preprocess plugins defined by the user"""
276        for item in self._preprocessors:
277            self = Model.preprocessor_ep.service(item).preprocess(self)
278       
279
280    def solution(self):
281        """Return the solution"""
282        soln = []
283        for i in range(len(self._var)):
284            soln.append( self._var[i].value )
285        return soln
286
287
288    def load(self, arg):
289        """ Load the model with data from a file or a Solution object """
290        if arg is None or type(arg) is str:
291           self._load_model_data(ModelData(filename=arg,model=self))
292           return True
293        elif type(arg) is ModelData:
294           self._load_model_data(arg)
295           return True
296        elif type(arg) is coopr.opt.SolverResults:
297           # if the solver status not one of either OK or Warning, then error.
298           if (arg.solver.status != coopr.opt.SolverStatus.ok) and (arg.solver.status != coopr.opt.SolverStatus.warning):
299              raise ValueError, "Cannot load a SolverResults object with bad status: "+str(arg.solver.status)
300           # but if there is a warning, print out a warning, as someone should probably take a look!
301           if (arg.solver.status == coopr.opt.SolverStatus.warning):
302              print "WARNING - Loading a SolverResults object with a warning status"
303           if len(arg.solution) > 0:
304              self._load_solution(arg.solution(0),symbol_map=arg.symbol_map)
305              return True
306           else:
307              return False
308        elif type(arg) is coopr.opt.Solution:
309           self._load_solution(arg)
310           return True
311        elif type(arg) in (tuple, list):
312           self._load_solution(arg)
313           return True
314        else:
315           raise ValueError, "Cannot load model with object of type "+str(type(arg))
316
317
318    def store_info(self, results):
319        """ Store model information into a SolverResults object """
320        results.problem.name = self.name
321        results.problem.number_of_variables = self.statistics.number_of_variables
322        results.problem.number_of_binary_variables = self.statistics.number_of_binary_variables
323        results.problem.number_of_integer_variables = self.statistics.number_of_integer_variables
324        results.problem.number_of_continuous_variables = self.statistics.number_of_continuous_variables
325        results.problem.number_of_constraints = self.statistics.number_of_constraints
326        results.problem.number_of_objectives = self.statistics.number_of_objectives
327
328    def _tuplize(self, data, setobj):
329        if data is None:            #pragma:nocover
330           return None
331        if setobj.dimen == 1:
332           return data
333        ans = {}
334        for key in data:
335          if type(data[key][0]) is tuple:
336             return data
337          ans[key] = tuplize(data[key], setobj.dimen, setobj.name)
338        return ans
339
340
341    def _load_model_data(self,modeldata):
342        """
343        Load declarations from a ModelData object.
344        """
345        #print "HERE _load_model_data",self._declarations
346        for declaration in self._declarations:
347            if declaration.type() is Model:
348                continue
349            tmp = declaration.name
350            if tmp in modeldata._default.keys():
351                if declaration.type() is Set:
352                    declaration.default = self._tuplize(modeldata._default[tmp],declaration)
353                else:
354                    declaration.default = modeldata._default[tmp]
355            if tmp in modeldata._data.keys():
356                #print "HERE", declaration.name, str(declaration.dimen),modeldata._data[tmp]
357                if declaration.type() is Set:
358                    data = self._tuplize(modeldata._data[tmp],declaration)
359                else:
360                    data = modeldata._data[tmp]
361            else:
362                data = None
363            if pyomo.debug("generate"):           #pragma:nocover
364                print "About to generate '"+declaration.name+"' with data: "+str(data)
365                self.pprint()
366            declaration.construct(data)
367            try:
368                pass
369            except Exception, err:
370                print "Error constructing declaration "+str(declaration.name)+" from data="+str(data)
371                print "ERROR: "+str(err)
372                raise err
373
374    def _load_solution(self, soln, symbol_map=None):
375        """
376        Load a solution.
377        """
378        # Load list or tuple into the variables, based on their order
379        if type(soln) in (list, tuple):
380            if len(soln) != len(self._var):
381                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))
382            for i in range(len(soln)):
383                self._var[i].value = soln[i]
384            return
385        #
386        # Load variable data
387        #
388        for name, entry in soln.variable.items():
389          #
390          # NOTE: the following is a hack, to handle the ONE_VAR_CONSTANT variable
391          #       that is necessary for the objective constant-offset terms.
392          #       probably should create a dummy variable in the model map at the same
393          #       time the objective expression is being constructed.
394          #
395          if name != "ONE_VAR_CONSTANT":
396             # translate the name first if there is a variable map associated with the input solution.
397             if symbol_map is not None:
398                name = symbol_map[name]
399             if name not in self._name_varmap:
400                names=""
401                raise KeyError, "Variable name '"+name+"' is not in model "+self.name+".  Valid variable names: "+str(self._name_varmap.keys())
402             if not isinstance(self._name_varmap[name],_VarValue):
403                raise TypeError, "Variable '"+name+"' in model '"+self.name+"' is type "+str(type(self._name_varmap[name]))
404             if self._name_varmap[name].fixed is True:
405                raise TypeError, "Variable '"+name+"' in model '"+self.name+"' is currently fixed - new value is not expected in solution"
406
407             var_value = self._name_varmap[name]
408             for _key in entry.keys():
409                key = _key[0].lower() + _key[1:] 
410                if key == 'value':
411                    var_value.value = entry.value
412                elif not key == 'id':
413                    setattr(var_value, key, getattr(entry, _key))
414        #
415        # Load constraint data
416        #
417        for name,entry in soln.constraint.items():
418             #
419             # This is a hack - see above.
420             #
421             if name.endswith('ONE_VAR_CONSTANT'):
422                continue
423             if symbol_map is not None:
424                name = symbol_map[name]
425             #
426             # This is a hack.  Is there a standard convention for constraint
427             # names that we can apply to all data formats?
428             #
429             if name[0:2] == 'c_':
430                _name = name[4:-1]
431             else:
432                _name = name
433
434             if _name not in self._name_conmap:
435                names=""
436                raise KeyError, "Constraint name '"+name+"' is not in model "+self.name+".  Valid constraint names: "+str(self._name_conmap.keys())
437             if not isinstance(self._name_conmap[_name],ConstraintData):
438                raise TypeError, "Constraint '"+name+"' in model '"+self.name+"' is type "+str(type(self._name_conmap[_name]))
439
440             constraint_data = self._name_conmap[_name]
441             for _key in entry.keys():
442                key = _key[0].lower() + _key[1:] 
443                if key == 'value':
444                    constraint_data.value = entry.value
445                elif not key == 'id':
446                    setattr(constraint_data, key, getattr(entry, _key))
447
448    def write(self,filename=None,format=ProblemFormat.cpxlp):
449        """
450        Write the model to a file, with a given format.
451        """
452        if format is None and not filename is None:
453            #
454            # Guess the format
455            #
456            format = guess_format(filename)
457        problem_writer = coopr.opt.WriterFactory(format)
458        if problem_writer is None:
459           raise ValueError, "Cannot write model in format \"" + str(format) + "\": no model writer registered for that format"
460        (fname, symbol_map) = problem_writer(self, filename)
461        if pyomo.debug("verbose"):
462           print "Writing model "+self.name+" to file '"+str(fname)+"'  with format "+str(format)
463        return fname, symbol_map
464
465
466    def pprint(self, filename=None, ostream=None):
467        """
468        Print a summary of the model info
469        """
470        if ostream is None:
471           ostream = sys.stdout
472        if filename is not None:
473           OUTPUT=open(filename,"w")
474           self.pprint(ostream=OUTPUT)
475           OUTPUT.close()
476           return
477        if ostream is None:
478           ostream = sys.stdout
479        #
480        # We hard-code the order of the core Pyomo modeling
481        # components, to ensure that the output follows the logical
482        # expected by a user.
483        #
484        items = [Set, RangeSet, Param, Var, Objective, Constraint]
485        for item in pyutilib.component.core.ExtensionPoint(IModelComponent):
486            if not item in items:
487                items.append(item)
488        for item in items:
489            if not item in self._component:
490                continue
491            keys = self._component[item].keys()
492            keys.sort()
493            #
494            # NOTE: these conditional checks should not be hard-coded.
495            #
496            print >>ostream, len(keys), item.__name__+" Declarations"
497            for key in keys:
498                self._component[item][key].pprint(ostream)
499            print >>ostream, ""
500        #
501        # Model Order
502        #
503        print >>ostream, len(self._declarations),"Declarations:",
504        for i in range(0,len(self._declarations)):
505          print >>ostream, self._declarations[i].name,
506        print >>ostream, ""
507
508
509    def display(self, filename=None, ostream=None):
510        """
511        Print the Pyomo model in a verbose format.
512        """
513        if filename is not None:
514           OUTPUT=open(filename,"w")
515           self.display(ostream=OUTPUT)
516           OUTPUT.close()
517           return
518        if ostream is None:
519           ostream = sys.stdout
520        print >>ostream, "Model "+self.name
521        print >>ostream, ""
522        print >>ostream, "  Variables:"
523        VAR = self.active_components(Var)
524        if len(VAR) == 0:
525            print >>ostream, "    None"
526        else:
527            for ndx in VAR:
528                VAR[ndx].display(prefix="    ",ostream=ostream)
529        print >>ostream, ""
530        print >>ostream, "  Objectives:"
531        OBJ = self.active_components(Objective)
532        if len(OBJ) == 0:
533            print >>ostream, "    None"
534        else:
535            for ndx in OBJ:
536                OBJ[ndx].display(prefix="    ",ostream=ostream)
537        print >>ostream, ""
538        CON = self.active_components(Constraint)
539        print >>ostream, "  Constraints:"
540        if len(CON) == 0:
541            print >>ostream, "    None"
542        else:
543            for ndx in CON:
544                CON[ndx].display(prefix="    ",ostream=ostream)
545
546ComponentRegistration("Model", Model, "Model objects can be used as a component of other models.")
Note: See TracBrowser for help on using the repository browser.