source: coopr.pyomo/stable/coopr/pyomo/base/PyomoModel.py @ 3182

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

Merged revisions 3114-3181 via svnmerge from
https://software.sandia.gov/svn/public/coopr/coopr.pyomo/trunk

........

r3116 | jwatson | 2010-10-18 14:08:00 -0600 (Mon, 18 Oct 2010) | 3 lines


First of a few commits involving an experimental linear compaction of the expression tree associated with a constraint, in order to save both memory and time.

........

r3117 | jwatson | 2010-10-18 14:09:13 -0600 (Mon, 18 Oct 2010) | 3 lines


Renaming linear representation module.

........

r3119 | jwatson | 2010-10-18 21:06:55 -0600 (Mon, 18 Oct 2010) | 3 lines


More exhaustive additions relating to linear expression encodings. No functional change to pyomo unless the --linearize-expressions options is specified. All tests are passing.

........

r3121 | jwatson | 2010-10-20 12:02:31 -0600 (Wed, 20 Oct 2010) | 3 lines


Fixing white-space issue when writing quadratic terms in LP files. Gurobi 4.0 beta wants more white-space than CPLEX, which is easy enough to accomodate.

........

r3123 | jwatson | 2010-10-20 13:02:06 -0600 (Wed, 20 Oct 2010) | 3 lines


Changing "integer" section label with the more technically correct "general" in the LP file writer. The former is not recognized by Gurobi, and actually does not appear in the CPLEX documentation either. I'm not sure where we got this, but the fix corrects/bypasses the issue entirely.

........

r3130 | wehart | 2010-10-20 20:44:17 -0600 (Wed, 20 Oct 2010) | 2 lines


Update of coopr.pyomo CHANGELOG for the 2.4 release.

........

r3132 | prsteel | 2010-10-21 15:30:40 -0600 (Thu, 21 Oct 2010) | 2 lines


Adding myself as a developer.

........

r3133 | jwatson | 2010-10-21 15:41:58 -0600 (Thu, 21 Oct 2010) | 5 lines


Various updates, mostly relating to linear expression representations debugging. Initial tests indicate very significant speed and memory reductions (at least for PH), with regression testing performed against a few initial benchmarks. No functional change if linear expressions are not enabled, which is the default


On the variable front, I have added output of variable Status in the pprint(), to facilitate debugging.

........

r3136 | khunter | 2010-10-22 10:19:32 -0600 (Fri, 22 Oct 2010) | 2 lines


Add name as a developer.

........

r3140 | jwatson | 2010-10-22 15:47:33 -0600 (Fri, 22 Oct 2010) | 3 lines


When constructing a linear representation, dump the canonical representation on the ConstraintData? class if it exists - these take up a large amount of space as well.

........

r3141 | jwatson | 2010-10-22 16:54:49 -0600 (Fri, 22 Oct 2010) | 3 lines


Added a "nochecking" keyword argument to Params. Intended strictly for use by algorithm developers, it by-passes the call to _valid_indexed_component, which was taking a huge amount of time in various PySP algorithms. Use sparingly and judiciously! May also apply in the near future to other aspects of setitem.

........

r3142 | jwatson | 2010-10-22 17:07:45 -0600 (Fri, 22 Oct 2010) | 3 lines


Suppression of index validation in Param::setitem when nochecking is enabled.

........

r3144 | jwatson | 2010-10-22 22:15:52 -0600 (Fri, 22 Oct 2010) | 3 lines


Update of test output baseline to reflect addition of variable status output in pretty-print.

........

r3149 | wehart | 2010-10-23 22:59:06 -0600 (Sat, 23 Oct 2010) | 3 lines


Changes to allow RangeSet? instances to be defined with
floating point start, stop and step arguments.

........

r3150 | wehart | 2010-10-23 22:59:31 -0600 (Sat, 23 Oct 2010) | 2 lines


Allowing string values to be elements of a Boolean set.

........

r3151 | wehart | 2010-10-23 23:00:47 -0600 (Sat, 23 Oct 2010) | 2 lines


Removing the name_str() method, which I didn't mean to commit.

........

r3153 | jwatson | 2010-10-24 22:14:15 -0600 (Sun, 24 Oct 2010) | 5 lines


Adding logic for LP writer when dealing with linearized expressions involving fixed variables.


Added "has_discrete_variables" method to PyomoModel?, to support query by MIP plugins - which often need this method to determine the type of solution information (e.g., duals) to extract.

........

r3158 | claird | 2010-10-25 12:01:21 -0600 (Mon, 25 Oct 2010) | 1 line


Added exception for multiple objectives - not currently handled by the NL writier

........

r3159 | claird | 2010-10-25 12:03:18 -0600 (Mon, 25 Oct 2010) | 1 line


Commented out second objective function so there are not multiple objectives in the example

........

r3166 | jwatson | 2010-10-26 19:42:59 -0600 (Tue, 26 Oct 2010) | 3 lines


Various performance-related enhancements, including a rework of NumericValue? and NumericConstant? internals.

........

r3167 | jwatson | 2010-10-26 20:05:25 -0600 (Tue, 26 Oct 2010) | 3 lines


Restoring older version of rangeset.py - the latest commit caused all kinds of test failures, for reasons I haven't had time to explore.

........

r3171 | jwatson | 2010-10-28 21:02:40 -0600 (Thu, 28 Oct 2010) | 3 lines


Fixing bug observed by John regarding passing of keyword arguments to the Constraint base class initializer.

........

r3181 | wehart | 2010-10-28 21:45:53 -0600 (Thu, 28 Oct 2010) | 2 lines


Updating changelog

........

  • Property svn:executable set to *
File size: 31.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
11__all__ = ['Model', 'ConcreteModel', 'AbstractModel']
12
13import array
14import copy
15import re
16import sys
17
18from plugin import *
19from numvalue import *
20
21import pyutilib.component.core
22from pyutilib.math import *
23from pyutilib.misc import quote_split, tuplize, Container
24
25from coopr.opt import ProblemFormat, ResultsFormat, guess_format
26from coopr.pyomo.base.var import _VarValue, VarStatus
27from coopr.pyomo.base.constraint import ConstraintData
28import coopr.opt
29from PyomoModelData import ModelData
30import pyomo
31
32from block import Block
33from component import SharedComponent
34from sets import Set
35
36
37class Model(Block):
38    """
39    An optimization model.  By default, this defers construction of components
40    until data is loaded.
41    """
42
43    pyutilib.component.core.alias("Model", 'Model objects can be used as a '  \
44                                  'component of other models.')
45
46    preprocessor_ep = pyutilib.component.core.ExtensionPoint(IPyomoPresolver)
47
48    def __init__ ( self, name='unknown', **kwargs ):
49        """Constructor"""
50        if kwargs.pop('_deep_copying', None):
51            # Hack for Python 2.4 compatibility
52            # Deep copy will copy all items as necessary, so no need to
53            # complete parsing
54            return
55
56        Block.__init__(self, ctype=Model)
57        #
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        #
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        # We make the default behavior of has_capabilities to lie and return
82        # True. We do this to allow the richest output formats when no
83        # solver is specified. These capabilities _will_ be overwritten
84        # when a solver is specified.
85        #
86        # We define _tempTrue since using self.has_capability = lambda x: True
87        # causes pickling errors.
88        #
89        self.has_capability = _tempTrue
90
91    def nvariables(self):
92        return self.statistics.number_of_variables
93
94    def variable(self, name):
95        if isinstance(name,basestring):
96            return self._name_varmap[name]
97        else:
98            return self._var[name-1]
99
100    def has_discrete_variables(self):
101
102       variables = self.active_components(Var)
103       for variable_name, variable in vars.values():
104          if isinstance(variable.domain, IntegerSet) or isinstance(variable.domain, BooleanSet):
105             return True
106       return False
107   
108
109    def variables(self):
110        return self._name_varmap
111
112    def nconstraints(self):
113        return self.statistics.number_of_constraints
114
115    def constraint(self, name):
116        if isinstance(name,basestring):
117            return self._name_conmap[name]
118        else:
119            return self._con[name-1]
120
121    def constraints(self):
122        return self._name_conmap
123
124    def nobjectives(self):
125        return self.statistics.number_of_objectives
126
127    def objective(self, name):
128        if isinstance(name,basestring):
129            return self._name_objmap[name]
130        else:
131            return self._obj[name-1]
132
133    def objectives(self):
134        return self._name_objmap
135
136    def num_used_variables(self):
137        return len(self._var)
138
139    def valid_problem_types(self):
140        return [ProblemFormat.pyomo]
141
142    def create(self, filename=None, name=None, namespace=None, namespaces=None, preprocess=True, simplify=True):
143        """
144        Create a concrete instance of this Model, possibly using data
145        read in from a file.
146        """
147
148        if self._defer_construction:
149            instance = self.clone()
150
151            if namespaces is None:
152                instance.load(filename, namespaces=[namespace], simplify=simplify)
153            else:
154                instance.load(filename, namespaces=namespaces, simplify=simplify)
155        else:
156            instance = self
157        if preprocess is True:
158           instance.preprocess()
159        if not name is None:
160            instance.name=name
161        return instance
162
163    def clone(self):
164        """Create a copy of this model"""
165
166        for name in self._declarations:
167            self._declarations[name].model = None
168
169        instance = copy.deepcopy(self)
170
171        for name in self._declarations:
172            self._declarations[name].model = self
173        for name in instance._declarations:
174            instance._declarations[name].model = instance
175        return instance
176
177    def reset(self):
178        for name in self._declarations:
179            self._declarations[name].reset()
180
181    def preprocess(self):
182        """Apply the preprocess plugins defined by the user"""
183        for item in self._preprocessors:
184            self = Model.preprocessor_ep.service(item).preprocess(self)
185
186    def solution(self):
187        """Return the solution"""
188        soln = []
189        for i in range(len(self._var)):
190            soln.append( self._var[i].value )
191        return soln
192
193    def load(self, arg, namespaces=[None], simplify=True):
194        """ Load the model with data from a file or a Solution object """
195        if arg is None or type(arg) is str:
196           self._load_model_data(ModelData(filename=arg,model=self), namespaces, simplify=simplify)
197           return True
198        elif type(arg) is ModelData:
199           self._load_model_data(arg, namespaces)
200           return True
201        elif type(arg) is coopr.opt.SolverResults:
202           # if the solver status not one of either OK or Warning, then error.
203           if (arg.solver.status != coopr.opt.SolverStatus.ok) and \
204              (arg.solver.status != coopr.opt.SolverStatus.warning):
205               msg = 'Cannot load a SolverResults object with bad status: %s'
206               raise ValueError, msg % str( arg.solver.status )
207
208           # but if there is a warning, print out a warning, as someone should
209           # probably take a look!
210           if (arg.solver.status == coopr.opt.SolverStatus.warning):
211               print 'WARNING - Loading a SolverResults object with a '       \
212                     'warning status'
213
214           if len(arg.solution) > 0:
215              self._load_solution(arg.solution(0),symbol_map=arg.symbol_map)
216              return True
217           else:
218              return False
219        elif type(arg) is coopr.opt.Solution:
220           self._load_solution(arg)
221           return True
222        elif type(arg) in (tuple, list):
223           self._load_solution(arg)
224           return True
225        else:
226            msg = "Cannot load model with object of type '%s'"
227            raise ValueError, msg % str( type(arg) )
228
229    def store_info(self, results):
230        """ Store model information into a SolverResults object """
231        results.problem.name = self.name
232
233        stat_keys = (
234          'number_of_variables',
235          'number_of_binary_variables',
236          'number_of_integer_variables',
237          'number_of_continuous_variables',
238          'number_of_constraints',
239          'number_of_objectives'
240        )
241
242        for key in stat_keys:
243            results.problem.__dict__[key] = self.statistics.__dict__[key]
244
245    def _tuplize(self, data, setobj):
246        if data is None:            #pragma:nocover
247           return None
248        if setobj.dimen == 1:
249           return data
250        ans = {}
251        for key in data:
252          if type(data[key][0]) is tuple:
253             return data
254          ans[key] = tuplize(data[key], setobj.dimen, setobj.name)
255        return ans
256
257    def _load_model_data(self, modeldata, namespaces, **kwds):
258        """
259        Load declarations from a ModelData object.
260        """
261        #
262        # Do some error checking
263        #
264        for namespace in namespaces:
265            if not namespace is None and not namespace in modeldata._data:
266                msg = "Cannot access undefined namespace: '%s'"
267                raise IOError, msg % namespace
268        #print "HERE _load_model_data",self._declarations
269        for tmp in self._declarations:
270            if self._declarations[tmp].type() is Model:
271                continue
272            declaration = self._declarations[tmp]
273            if tmp in modeldata._default.keys():
274                if declaration.type() is Set:
275                    declaration.default = self._tuplize(modeldata._default[tmp],
276                                                        declaration)
277                else:
278                    declaration.default = modeldata._default[tmp]
279            data = None
280
281            for namespace in namespaces:
282                #print "HERE", declaration.name, namespace, modeldata._data
283                if tmp in modeldata._data.get(namespace,{}).keys():
284                    #print "HERE", declaration.name, str(declaration.dimen), \
285                    #modeldata._data[namespace][tmp]
286                    if declaration.type() is Set:
287                        data = self._tuplize(modeldata._data[namespace][tmp],
288                                             declaration)
289                    else:
290                        data = modeldata._data[namespace][tmp]
291                #print "HERE X", data
292                if not data is None:
293                    break
294            if pyomo.debug("generate"):                         #pragma:nocover
295                msg = "About to generate '%s' with data: %s"
296                print msg % ( declaration.name, str(data) )
297                self.pprint()
298
299            try:
300                if isinstance(declaration, coopr.pyomo.base.constraint.Constraint) or isinstance(declaration, coopr.pyomo.base.objective.Objective):
301                   declaration.construct(data, simplify=kwds.get('simplify', True))
302                else:
303                   declaration.construct(data)
304            except ValueError, err:
305                msg = ("Error constructing declaration '%s' from data=%s" + \
306                       "\nSource error: %s") % \
307                       (str(declaration.name), str(data), str(err))
308                raise ValueError, msg
309            except Exception, err:
310                msg = ("Error constructing declaration '%s' from data=%s" + \
311                       "\nSource error: %s") % \
312                       (str(declaration.name), str(data), str(err))
313                raise RuntimeError, msg
314
315    def _load_solution(self, soln, symbol_map=None):
316        """
317        Load a solution.
318        """
319        # Load list or tuple into the variables, based on their order
320        if type(soln) in (list, tuple):
321            if len(soln) != len(self._var):
322                msg = 'Attempting to load a list/tuple solution into a '      \
323                      'model, but its length is %d while the model has %d '   \
324                      'variables' % (len(soln), len(self._var))
325                raise ValueError, msg % ( len(soln), len(self._var) )
326            for i in range(len(soln)):
327                self._var[i].value = soln[i]
328            return
329        #
330        # Load variable data
331        #
332        for name, entry in soln.variable.items():
333
334            # NOTE: the following is a hack, to handle the ONE_VAR_CONSTANT
335            #    variable that is necessary for the objective constant-offset
336            #    terms.  Probably should create a dummy variable in the model
337            #    map at the same time the objective expression is being
338            #    constructed.
339
340          if name != "ONE_VAR_CONSTANT":
341
342             # translate the name first if there is a variable map associated
343             # with the input solution.
344             if symbol_map is not None:
345                name = symbol_map[name]
346             if name not in self._name_varmap:
347                msg = "Variable name '%s' is not in model '%s'.  Valid "      \
348                      'variable names: %s'
349                raise KeyError, msg % (
350                          name, self.name, str(self._name_varmap.keys()) )
351
352             if not isinstance(self._name_varmap[name],_VarValue):
353                 msg = "Variable '%s' in model '%s' is type %s"
354                 raise TypeError, msg % (
355                     name, self.name, str(type(self._name_varmap[name])) )
356
357             if self._name_varmap[name].fixed is True:
358                 msg = "Variable '%s' in model '%s' is currently fixed - new" \
359                       ' value is not expected in solution'
360                 raise TypeError, msg % ( name, self.name )
361
362             if self._name_varmap[name].status is not VarStatus.used:
363                 msg = "Variable '%s' in model '%s' is not currently used - no" \
364                       ' value is expected in solution'
365                 raise TypeError, msg % ( name, self.name )
366
367             var_value = self._name_varmap[name]
368             for _key in entry.keys():
369                key = _key[0].lower() + _key[1:]
370                if key == 'value':
371                    var_value.value = entry.value
372                elif not key == 'id':
373                    setattr(var_value, key, getattr(entry, _key))
374        #
375        # Load constraint data
376        #
377        for name,entry in soln.constraint.items():
378             #
379             # This is a hack - see above.
380             #
381             if name.endswith('ONE_VAR_CONSTANT'):
382                continue
383             if symbol_map is not None:
384                name = symbol_map[name]
385             #
386             # This is a hack.  Is there a standard convention for constraint
387             # names that we can apply to all data formats?
388             #
389             if name[0:2] == 'c_':
390                _name = name[4:-1]
391             else:
392                _name = name
393
394             if _name not in self._name_conmap:
395                msg = "Constraint name '%s' is not in model '%s'.  Valid "    \
396                      'constraint names: %s'
397                raise KeyError, msg % (
398                              name, self.name, str(self._name_conmap.keys()) )
399
400             if not isinstance(self._name_conmap[_name],ConstraintData):
401                 msg = "Constraint '%s' in model '%s' is type %s"
402                 raise TypeError, msg % (
403                        name, self.name, str(type(self._name_conmap[_name])) )
404
405             constraint_data = self._name_conmap[_name]
406             for _key in entry.keys():
407                key = _key[0].lower() + _key[1:]
408                if key == 'value':
409                    constraint_data.value = entry.value
410                elif not key == 'id':
411                    setattr(constraint_data, key, getattr(entry, _key))
412
413    def write(self,filename=None,format=ProblemFormat.cpxlp):
414        """
415        Write the model to a file, with a given format.
416        """
417        if format is None and not filename is None:
418            #
419            # Guess the format
420            #
421            format = guess_format(filename)
422        #print "X",str(format),coopr.opt.WriterFactory.services()
423        problem_writer = coopr.opt.WriterFactory(format)
424        if problem_writer is None:
425            msg = "Cannot write model in format '%s'': no model writer "      \
426                  'registered for that format'
427            raise ValueError, msg % str(format)
428
429        (fname, symbol_map) = problem_writer(self, filename)
430        if pyomo.debug("verbose"):
431            msg = "Writing model '%s' to file '%s' with format %s"
432            print msg % ( self.name, str(fname), str(format) )
433
434        return fname, symbol_map
435
436    def to_standard_form(self):
437        """
438        Produces a standard-form representation of the model. Returns
439        the coefficient matrix (A), the cost vector (c), and the
440        constraint vector (b), where the 'standard form' problem is
441
442        min/max c'x
443        s.t.    Ax = b
444                x >= 0
445
446        All three returned values are instances of the array.array
447        class, and store Python floats (C doubles).
448        """
449
450        from coopr.pyomo.expr import generate_canonical_repn
451
452
453        # We first need to create an map of all variables to their column
454        # number
455        colID = {}
456        ID2name = {}
457        id = 0
458        tmp = self.variables().keys()
459        tmp.sort()
460
461        for v in tmp:
462            colID[v] = id
463            ID2name[id] = v
464            id += 1
465
466        # First we go through the constraints and introduce slack and excess
467        # variables to eliminate inequality constraints
468        #
469        # N.B. Structure heirarchy:
470        #
471        # active_components: {class: {attr_name: object}}
472        # object -> Constraint: ._data: {ndx: ConstraintData}
473        # ConstraintData: .lower, .body, .upper
474        #
475        # So, altogether, we access a lower bound via
476        #
477        # model.active_components()[Constraint]['con_name']['index'].lower
478        #
479        # {le,ge,eq}Constraints are
480        # {constraint_name: {index: {variable_or_none: coefficient}} objects
481        # that represent each constraint. None in the innermost dictionary
482        # represents the constant term.
483        #
484        # i.e.
485        #
486        # min  x1 + 2*x2 +          x4
487        # s.t. x1                         = 1
488        #           x2   + 3*x3          <= -1
489        #      x1 +                 x4   >= 3
490        #      x1 + 2*x2 +      +   3*x4 >= 0
491        #
492        #
493        # would be represented as (modulo the names of the variables,
494        # constraints, and indices)
495        #
496        # eqConstraints = {'c1': {None: {'x1':1, None:-1}}}
497        # leConstraints = {'c2': {None: {'x2':1, 'x3':3, None:1}}}
498        # geConstraints = {'c3': {None: {'x1':1, 'x4':1, None:-3}},
499        #                  'c4': {None: {'x1':1, 'x2':2, 'x4':1, None:0}}}
500        #
501        # Note the we have the luxury of dealing only with linear terms.
502        leConstraints = {}
503        geConstraints = {}
504        eqConstraints = {}
505        objectives = {}
506        # For each registered component
507        for c in self.active_components():
508
509            # Get all subclasses of ConstraintBase
510            if issubclass(c, ConstraintBase):
511                cons = self.active_components(c)
512
513                # Get the name of the constraint, and the constraint set itself
514                for con_set_name in cons:
515                    con_set = cons[con_set_name]
516
517                    # For each indexed constraint in the constraint set
518                    for ndx in con_set._data:
519                        con = con_set._data[ndx]
520
521                        # Process the body
522                        terms = self._process_canonical_repn(
523                            generate_canonical_repn(con.body))
524
525                        # Process the bounds of the constraint
526                        if con._equality:
527                            # Equality constraint, only check lower bound
528                            lb = self._process_canonical_repn(
529                                generate_canonical_repn(con.lower))
530
531                            # Update terms
532                            for k in lb:
533                                v = lb[k]
534                                if k in terms:
535                                    terms[k] -= v
536                                else:
537                                    terms[k] = -v
538
539                            # Add constraint to equality constraints
540                            eqConstraints[(con_set_name, ndx)] = terms
541                        else:
542
543                            # Process upper bounds (<= constraints)
544                            if con.upper is not None:
545                                # Less than or equal to constraint
546                                tmp = dict(terms)
547
548                                ub = self._process_canonical_repn(
549                                    generate_canonical_repn(con.upper))
550
551                                # Update terms
552                                for k in ub:
553                                    if k in terms:
554                                        tmp[k] -= ub[k]
555                                    else:
556                                        tmp[k] = -ub[k]
557
558                                # Add constraint to less than or equal to
559                                # constraints
560                                leConstraints[(con_set_name, ndx)] = tmp
561
562                            # Process lower bounds (>= constraints)
563                            if con.lower is not None:
564                                # Less than or equal to constraint
565                                tmp = dict(terms)
566
567                                lb = self._process_canonical_repn(
568                                    generate_canonical_repn(con.lower))
569
570                                # Update terms
571                                for k in lb:
572                                    if k in terms:
573                                        tmp[k] -= lb[k]
574                                    else:
575                                        tmp[k] = -lb[k]
576
577                                # Add constraint to less than or equal to
578                                # constraints
579                                geConstraints[(con_set_name, ndx)] = tmp
580            elif issubclass(c, Objective):
581                # Process objectives
582                objs = self.active_components(c)
583
584                # Get the name of the objective, and the objective set itself
585                for obj_set_name in objs:
586                    obj_set = objs[obj_set_name]
587
588                    # For each indexed objective in the objective set
589                    for ndx in obj_set._data:
590                        obj = obj_set._data[ndx]
591                        # Process the objective
592                        terms = self._process_canonical_repn(
593                            generate_canonical_repn(obj.expr))
594
595                        objectives[(obj_set_name, ndx)] = terms
596
597
598        # We now have all the constraints. Add a slack variable for every
599        # <= constraint and an excess variable for every >= constraint.
600        nSlack = len(leConstraints)
601        nExcess = len(geConstraints)
602
603        nConstraints = len(leConstraints) + len(geConstraints) + \
604                       len(eqConstraints)
605        nVariables = len(colID) + nSlack + nExcess
606        nRegVariables = len(colID)
607
608        # Make the arrays
609        coefficients = array.array("d", [0]*nConstraints*nVariables)
610        constraints = array.array("d", [0]*nConstraints)
611        costs = array.array("d", [0]*nVariables)
612
613        # Populate the coefficient matrix
614        constraintID = 0
615
616        # Add less than or equal to constraints
617        for ndx in leConstraints:
618            con = leConstraints[ndx]
619            for termKey in con:
620                coef = con[termKey]
621
622                if termKey is None:
623                    # Constraint coefficient
624                    constraints[constraintID] = -coef
625                else:
626                    # Variable coefficient
627                    col = colID[termKey]
628                    coefficients[constraintID*nVariables + col] = coef
629
630            # Add the slack
631            coefficients[constraintID*nVariables + nRegVariables + \
632                        constraintID] = 1
633            constraintID += 1
634
635        # Add greater than or equal to constraints
636        for ndx in geConstraints:
637            con = geConstraints[ndx]
638            for termKey in con:
639                coef = con[termKey]
640
641                if termKey is None:
642                    # Constraint coefficient
643                    constraints[constraintID] = -coef
644                else:
645                    # Variable coefficient
646                    col = colID[termKey]
647                    coefficients[constraintID*nVariables + col] = coef
648
649            # Add the slack
650            coefficients[constraintID*nVariables + nRegVariables + \
651                        constraintID] = -1
652            constraintID += 1
653
654        # Add equality constraints
655        for ndx in eqConstraints:
656            con = eqConstraints[ndx]
657            for termKey in con:
658                coef = con[termKey]
659
660                if termKey is None:
661                    # Constraint coefficient
662                    constraints[constraintID] = -coef
663                else:
664                    # Variable coefficient
665                    col = colID[termKey]
666                    coefficients[constraintID*nVariables + col] = coef
667
668            constraintID += 1
669
670        # Determine cost coefficients
671        for obj_name in objectives:
672            obj = objectives[obj_name]
673            for var in obj:
674                costs[colID[var]] = obj[var]
675
676        # Print the model
677        #
678        # The goal is to print
679        #
680        #         var1   var2   var3   ...
681        #       +--                     --+
682        #       | cost1  cost2  cost3  ...|
683        #       +--                     --+
684        #       +--                     --+ +-- --+
685        # con1  | coef11 coef12 coef13 ...| | eq1 |
686        # con2  | coef21 coef22 coef23 ...| | eq2 |
687        # con2  | coef31 coef32 coef33 ...| | eq3 |
688        #  .    |   .      .      .   .   | |  .  |
689        #  .    |   .      .      .    .  | |  .  |
690        #  .    |   .      .      .     . | |  .  |
691
692        constraintPadding = 2
693        numFmt = "% 1.4f"
694        altFmt = "% 1.1g"
695        maxColWidth = max(len(numFmt % 0.0), len(altFmt % 0.0))
696        maxConstraintColWidth = max(len(numFmt % 0.0), len(altFmt % 0.0))
697
698        # Generate constraint names
699        maxConNameLen = 0
700        conNames = []
701        for name in leConstraints:
702            strName = str(name)
703            if len(strName) > maxConNameLen:
704                maxConNameLen = len(strName)
705            conNames.append(strName)
706        for name in geConstraints:
707            strName = str(name)
708            if len(strName) > maxConNameLen:
709                maxConNameLen = len(strName)
710            conNames.append(strName)
711        for name in eqConstraints:
712            strName = str(name)
713            if len(strName) > maxConNameLen:
714                maxConNameLen = len(strName)
715            conNames.append(strName)
716
717        # Generate the variable names
718        varNames = [None]*len(colID)
719        for name in colID:
720            tmp_name = " " + name
721            if len(tmp_name) > maxColWidth:
722                maxColWidth = len(tmp_name)
723            varNames[colID[name]] = tmp_name
724        for i in xrange(0, nSlack):
725            tmp_name = " _slack_%i" % i
726            if len(tmp_name) > maxColWidth:
727                maxColWidth = len(tmp_name)
728            varNames.append(tmp_name)
729        for i in xrange(0, nExcess):
730            tmp_name = " _excess_%i" % i
731            if len(tmp_name) > maxColWidth:
732                maxColWidth = len(tmp_name)
733            varNames.append(tmp_name)
734
735        # Variable names
736        line = " "*maxConNameLen + (" "*constraintPadding) + " "
737        for col in xrange(0, nVariables):
738            # Format entry
739            token = varNames[col]
740
741            # Pad with trailing whitespace
742            token += " "*(maxColWidth - len(token))
743
744            # Add to line
745            line += " " + token + " "
746        print line
747
748        # Cost vector
749        print " "*maxConNameLen + (" "*constraintPadding) + "+--" + \
750              " "*((maxColWidth+2)*nVariables - 4) + "--+"
751        line = " "*maxConNameLen + (" "*constraintPadding) + "|"
752        for col in xrange(0, nVariables):
753            # Format entry
754            token = numFmt % costs[col]
755            if len(token) > maxColWidth:
756                token = altFmt % costs[col]
757
758            # Pad with trailing whitespace
759            token += " "*(maxColWidth - len(token))
760
761            # Add to line
762            line += " " + token + " "
763        line += "|"
764        print line
765        print " "*maxConNameLen + (" "*constraintPadding) + "+--" + \
766              " "*((maxColWidth+2)*nVariables - 4) + "--+"
767
768        # Constraints
769        print " "*maxConNameLen + (" "*constraintPadding) + "+--" + \
770              " "*((maxColWidth+2)*nVariables - 4) + "--+" + \
771              (" "*constraintPadding) + "+--" + \
772              (" "*(maxConstraintColWidth-1)) + "--+"
773        for row in xrange(0, nConstraints):
774            # Print constraint name
775            line = conNames[row] + (" "*constraintPadding) + (" "*(maxConNameLen - len(conNames[row]))) + "|"
776
777            # Print each coefficient
778            for col in xrange(0, nVariables):
779                # Format entry
780                token = numFmt % coefficients[nVariables*row + col]
781                if len(token) > maxColWidth:
782                    token = altFmt % coefficients[nVariables*row + col]
783
784                # Pad with trailing whitespace
785                token += " "*(maxColWidth - len(token))
786
787                # Add to line
788                line += " " + token + " "
789
790            line += "|" + (" "*constraintPadding) + "|"
791
792            # Add constraint vector
793            token = numFmt % constraints[row]
794            if len(token) > maxConstraintColWidth:
795                token = altFmt % constraints[row]
796
797            # Pad with trailing whitespace
798            token += " "*(maxConstraintColWidth - len(token))
799
800            line += " " + token + "  |"
801            print line
802        print " "*maxConNameLen + (" "*constraintPadding) + "+--" + \
803              " "*((maxColWidth+2)*nVariables - 4) + "--+" + \
804              (" "*constraintPadding) + "+--" + (" "*(maxConstraintColWidth-1))\
805              + "--+"
806
807        return (coefficients, costs, constraints)
808
809    def _process_canonical_repn(self, expr):
810        """
811        Returns a dictionary of {var_name_or_None: coef} values
812        """
813
814        terms = {}
815
816        # Get the variables from the canonical representation
817        vars = expr.pop(-1, {})
818
819        # Find the linear terms
820        linear = expr.pop(1, {})
821        for k in linear:
822            # FrozeDicts don't support (k, v)-style iteration
823            v = linear[k]
824
825            # There's exactly 1 variable in each term
826            terms[vars[k.keys()[0]].label] = v
827
828        # Get the constant term, if present
829        const = expr.pop(0, {})
830        if None in const:
831            terms[None] = const[None]
832
833        if len(expr) != 0:
834            raise TypeError, "Nonlinear terms in expression"
835
836        return terms
837
838
839def _tempTrue(x):
840    """
841    Defined to return True to all inquiries
842    """
843    return True
844
845
846class ConcreteModel(Model):
847    """
848    A concrete optimization model that does not defer construction of
849    components.
850    """
851
852    def __init__(self, *args, **kwds):
853        Model.__init__(self, *args, **kwds)
854        self._defer_construction=False
855
856    pyutilib.component.core.alias("ConcreteModel", 'A concrete optimization model that '\
857              'does not defer construction of components.')
858
859class AbstractModel(Model):
860    """
861    An abstract optimization model that defers construction of
862    components.
863    """
864
865    def __init__(self, *args, **kwds):
866        Model.__init__(self, *args, **kwds)
867        self._defer_construction=True
868
869    pyutilib.component.core.alias("AbstractModel", 'An abstract optimization model that '\
870              'defers construction of components.')
871
Note: See TracBrowser for help on using the repository browser.