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

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

Misc rework of variable presolving. Now, model
statistics are stored in a more obvious location, and using more
verbose names.

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