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

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

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

........

r3188 | jwatson | 2010-10-29 08:09:35 -0600 (Fri, 29 Oct 2010) | 3 lines


Eliminating initial domain check when constructing numeric constants and the default domain (Reals) is specified.

........

r3203 | jwatson | 2010-10-29 14:46:18 -0600 (Fri, 29 Oct 2010) | 3 lines


Fixing bugs in has_discrete_variables() method of PyomoModel?.

........

r3211 | jdsiiro | 2010-11-01 14:49:11 -0600 (Mon, 01 Nov 2010) | 4 lines


bugfixes for Blocks:

  • avoid infinite loop when adding a block to a model
  • support pretty printing of user-defined components

........

r3212 | jdsiiro | 2010-11-02 15:17:13 -0600 (Tue, 02 Nov 2010) | 5 lines


  • cleaning up the management of Block._parent_block and Component.model attributes. Adding & removing blocks now updates the model attribute on all children
  • renaming Block._setattr_exec -> Block._add_component

........

r3213 | jdsiiro | 2010-11-03 10:47:06 -0600 (Wed, 03 Nov 2010) | 2 lines


Bugfix to the PyomoLogHandler? for python 2.4 compatibility

........

r3214 | jdsiiro | 2010-11-03 15:20:47 -0600 (Wed, 03 Nov 2010) | 3 lines


There is no point logging a warning when the problem is encountered
generating a logging.info message: log the warning at the info level.

........

r3215 | wehart | 2010-11-04 23:05:17 -0600 (Thu, 04 Nov 2010) | 3 lines


Adding a simple knapsack example to illustrate the difference between
a concrete and abstract model.

........

r3216 | jwatson | 2010-11-05 09:39:19 -0600 (Fri, 05 Nov 2010) | 3 lines


Fixing error diagnostic when indexing a variable with a bad index.

........

r3219 | jwatson | 2010-11-05 16:01:23 -0600 (Fri, 05 Nov 2010) | 3 lines


Supressing a validation test with NumericConstant?. If the user specifies a value, we are (now) assuming it is actually a numeric value - otherwise, the domain check significantly inflates the run-time associated with expression tree creation. This needs to be revisited in the Coopr 2.5 re-write.

........

r3226 | wehart | 2010-11-06 21:32:59 -0600 (Sat, 06 Nov 2010) | 2 lines


Setting up example, which was never converted.

........

r3233 | wehart | 2010-11-12 15:56:28 -0700 (Fri, 12 Nov 2010) | 4 lines


Migrating OS-specific functionality into coopr.os


Adding coopr.os to the dev.ini config file.

........

r3242 | wehart | 2010-11-13 01:28:57 -0700 (Sat, 13 Nov 2010) | 4 lines


Type fix.


Updating error message.

........

r3244 | wehart | 2010-11-13 10:44:26 -0700 (Sat, 13 Nov 2010) | 2 lines


Skipping OSiL writer when not defined.

........

r3246 | wehart | 2010-11-13 10:54:15 -0700 (Sat, 13 Nov 2010) | 2 lines


bug fix.

........

r3247 | wehart | 2010-11-13 11:14:01 -0700 (Sat, 13 Nov 2010) | 2 lines


Bug fix.

........

r3248 | jwatson | 2010-11-17 13:51:47 -0700 (Wed, 17 Nov 2010) | 3 lines


Interim fixes to output of quadratic terms in LP writer - more to do, but at least the basic examples now work.

........

r3254 | jwatson | 2010-11-19 13:19:19 -0700 (Fri, 19 Nov 2010) | 3 lines


Fixed bug in LP writer involving quadratic terms involving two distinct variables. Added two new quadratic examples.

........

r3257 | jwatson | 2010-11-19 13:59:35 -0700 (Fri, 19 Nov 2010) | 3 lines


Fixing diagnostic error message when attempting to solve quadratic programs with GLPK - code for generating message was not syntatically legal.

........

r3268 | jwatson | 2010-12-01 15:08:28 -0700 (Wed, 01 Dec 2010) | 3 lines


Fixing issues with the Piecewise construct when breakpoints and slopes are generated via rules. Works now (on a sample of size 1 - the newly added example5.py) for non-indexed rules, likely broken for indexed breakpoint/slope rules.

........

r3272 | jwatson | 2010-12-02 13:53:51 -0700 (Thu, 02 Dec 2010) | 3 lines


Adding omitted pprint() method for SOS constraints - identified while debugging a piecewise issue.

........

r3274 | jwatson | 2010-12-02 16:32:29 -0700 (Thu, 02 Dec 2010) | 3 lines


Adding example of Piecewise construct using breakpoint and slope rules, as opposed to explicit/direct lists.

........

r3276 | jwatson | 2010-12-03 14:06:40 -0700 (Fri, 03 Dec 2010) | 3 lines


Some progress toward functional indexed Piecewise components.

........

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