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

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

Reworking the symbol_map generated for the NL file.

Fixing the use of this within PyomoModel? when loading data.

Adding logic to specify the 'solver' option when an optimization interface
is specified (e.g. asl:cplexamp).

  • Property svn:executable set to *
File size: 19.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.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             print "HERE",name
396             var_value = self._name_varmap[name]
397             for _key in entry.keys():
398                key = _key[0].lower() + _key[1:] 
399                if key == 'value':
400                    var_value.value = entry.value
401                elif not key == 'id':
402                    setattr(var_value, key, getattr(entry, _key))
403        #
404        # Load constraint data
405        #
406        for name,entry in soln.constraint.items():
407             #
408             # This is a hack - see above.
409             #
410             if name.endswith('ONE_VAR_CONSTANT'):
411                continue
412             if symbol_map is not None:
413                name = symbol_map[name]
414             #
415             # This is a hack.  Is there a standard convention for constraint
416             # names that we can apply to all data formats?
417             #
418             if name[0:2] == 'c_':
419                _name = name[4:-1]
420             else:
421                _name = name
422
423             if _name not in self._name_conmap:
424                names=""
425                raise KeyError, "Constraint name '"+name+"' is not in model "+self.name+".  Valid constraint names: "+str(self._name_conmap.keys())
426             if not isinstance(self._name_conmap[_name],ConstraintData):
427                raise TypeError, "Constraint '"+name+"' in model '"+self.name+"' is type "+str(type(self._name_conmap[_name]))
428
429             constraint_data = self._name_conmap[_name]
430             for _key in entry.keys():
431                key = _key[0].lower() + _key[1:] 
432                if key == 'value':
433                    constraint_data.value = entry.value
434                elif not key == 'id':
435                    setattr(constraint_data, key, getattr(entry, _key))
436
437    def write(self,filename=None,format=ProblemFormat.cpxlp):
438        """
439        Write the model to a file, with a given format.
440        """
441        if format is None and not filename is None:
442            #
443            # Guess the format
444            #
445            format = guess_format(filename)
446        problem_writer = coopr.opt.WriterFactory(format)
447        if problem_writer is None:
448           raise ValueError, "Cannot write model in format \"" + str(format) + "\": no model writer registered for that format"
449        (fname, symbol_map) = problem_writer(self, filename)
450        if pyomo.debug("verbose"):
451           print "Writing model "+self.name+" to file '"+str(fname)+"'  with format "+str(format)
452        return fname, symbol_map
453
454
455    def pprint(self, filename=None, ostream=None):
456        """
457        Print a summary of the model info
458        """
459        if ostream is None:
460           ostream = sys.stdout
461        if filename is not None:
462           OUTPUT=open(filename,"w")
463           self.pprint(ostream=OUTPUT)
464           OUTPUT.close()
465           return
466        if ostream is None:
467           ostream = sys.stdout
468        #
469        # We hard-code the order of the core Pyomo modeling
470        # components, to ensure that the output follows the logical
471        # expected by a user.
472        #
473        items = [Set, RangeSet, Param, Var, Objective, Constraint]
474        for item in pyutilib.component.core.ExtensionPoint(IModelComponent):
475            if not item in items:
476                items.append(item)
477        for item in items:
478            if not item in self._component:
479                continue
480            keys = self._component[item].keys()
481            keys.sort()
482            #
483            # NOTE: these conditional checks should not be hard-coded.
484            #
485            print >>ostream, len(keys), item.__name__+" Declarations"
486            for key in keys:
487                self._component[item][key].pprint(ostream)
488            print >>ostream, ""
489        #
490        # Model Order
491        #
492        print >>ostream, len(self._declarations),"Declarations:",
493        for i in range(0,len(self._declarations)):
494          print >>ostream, self._declarations[i].name,
495        print >>ostream, ""
496
497
498    def display(self, filename=None, ostream=None):
499        """
500        Print the Pyomo model in a verbose format.
501        """
502        if filename is not None:
503           OUTPUT=open(filename,"w")
504           self.display(ostream=OUTPUT)
505           OUTPUT.close()
506           return
507        if ostream is None:
508           ostream = sys.stdout
509        print >>ostream, "Model "+self.name
510        print >>ostream, ""
511        print >>ostream, "  Variables:"
512        VAR = self.active_components(Var)
513        if len(VAR) == 0:
514            print >>ostream, "    None"
515        else:
516            for ndx in VAR:
517                VAR[ndx].display(prefix="    ",ostream=ostream)
518        print >>ostream, ""
519        print >>ostream, "  Objectives:"
520        OBJ = self.active_components(Objective)
521        if len(OBJ) == 0:
522            print >>ostream, "    None"
523        else:
524            for ndx in OBJ:
525                OBJ[ndx].display(prefix="    ",ostream=ostream)
526        print >>ostream, ""
527        CON = self.active_components(Constraint)
528        print >>ostream, "  Constraints:"
529        if len(CON) == 0:
530            print >>ostream, "    None"
531        else:
532            for ndx in CON:
533                CON[ndx].display(prefix="    ",ostream=ostream)
534
535ComponentRegistration("Model", Model, "Model objects can be used as a component of other models.")
Note: See TracBrowser for help on using the repository browser.