1#  ___________________________________________________________________________
2#
3#  Pyomo: Python Optimization Modeling Objects
4#  Copyright 2017 National Technology and Engineering Solutions of Sandia, LLC
5#  Under the terms of Contract DE-NA0003525 with National Technology and
6#  Engineering Solutions of Sandia, LLC, the U.S. Government retains certain
7#  rights in this software.
8#  This software is distributed under the 3-clause BSD License.
9#  ___________________________________________________________________________
10
11#
12# Problem Writer for BARON .bar Format Files
13#
14
15import itertools
16import logging
17import math
18from io import StringIO
19
20from pyomo.common.backports import nullcontext
21from pyomo.common.collections import OrderedSet
22from pyomo.opt import ProblemFormat
23from pyomo.opt.base import AbstractProblemWriter, WriterFactory
24from pyomo.core.expr.numvalue import (
25    value, native_numeric_types, native_types, nonpyomo_leaf_types,
26)
27from pyomo.core.expr import current as EXPR
28from pyomo.core.base import (SortComponents,
29                             SymbolMap,
30                             ShortNameLabeler,
31                             NumericLabeler,
32                             Constraint,
33                             Objective,
34                             Param)
35from pyomo.core.base.component import ActiveComponent
36#CLH: EXPORT suffixes "constraint_types" and "branching_priorities"
37#     pass their respective information to the .bar file
38import pyomo.core.base.suffix
39import pyomo.core.kernel.suffix
40from pyomo.core.kernel.block import IBlock
41from pyomo.repn.util import valid_expr_ctypes_minlp, \
42    valid_active_ctypes_minlp, ftoa
43
44logger = logging.getLogger('pyomo.core')
45
46#
47# A visitor pattern that creates a string for an expression
48# that is compatible with the BARON syntax.
49#
50class ToBaronVisitor(EXPR.ExpressionValueVisitor):
51
52    def __init__(self, variables, smap):
53        super(ToBaronVisitor, self).__init__()
54        self.variables = variables
55        self.smap = smap
56
57    def visit(self, node, values):
58        """ Visit nodes that have been expanded """
59        tmp = []
60        for i,val in enumerate(values):
61            arg = node._args_[i]
62
63            if arg is None:
64                tmp.append('Undefined')                 # TODO: coverage
65            else:
66                parens = False
67                if val and val[0] in '-+':
68                    parens = True
69                elif arg.__class__ in native_numeric_types:
70                    pass
71                elif arg.__class__ in nonpyomo_leaf_types:
72                    val = "'{0}'".format(val)
73                elif arg.is_expression_type():
74                    if node._precedence() < arg._precedence():
75                        parens = True
76                    elif node._precedence() == arg._precedence():
77                        if i == 0:
78                            parens = node._associativity() != 1
79                        elif i == len(node._args_)-1:
80                            parens = node._associativity() != -1
81                        else:
82                            parens = True
83                if parens:
84                    tmp.append("({0})".format(val))
85                else:
86                    tmp.append(val)
87
88        if node.__class__ in EXPR.NPV_expression_types:
89            return ftoa(value(node))
90
91        if node.__class__ is EXPR.LinearExpression:
92            for v in node.linear_vars:
93                self.variables.add(id(v))
94
95        if node.__class__ in {
96                EXPR.ProductExpression, EXPR.MonomialTermExpression}:
97            if tmp[0] in node._to_string.minus_one:
98                return "- {0}".format(tmp[1])
99            if tmp[0] in node._to_string.one:
100                return tmp[1]
101            return "{0} * {1}".format(tmp[0], tmp[1])
102        elif node.__class__ is EXPR.PowExpression:
103            x,y = node.args
104            if type(x) not in native_types and not x.is_fixed() and \
105               type(y) not in native_types and not y.is_fixed():
106                # Per the BARON manual, x ^ y is allowed as long as x
107                # and y are not both variables
108                return "exp(({1}) * log({0}))".format(tmp[0], tmp[1])
109            else:
110                return "{0} ^ {1}".format(tmp[0], tmp[1])
111        elif node.__class__ is EXPR.UnaryFunctionExpression:
112            if node.name == "sqrt":
113                return "{0} ^ 0.5".format(tmp[0])
114            elif node.name == 'log10':
115                return "{0} * log({1})".format(math.log10(math.e), tmp[0])
116            elif node.name in {'exp','log'}:
117                return node._to_string(tmp, None, self.smap, True)
118            else:
119                raise RuntimeError(
120                    'The BARON .BAR format does not support the unary '
121                    'function "%s".' % (node.name,))
122        elif node.__class__ is EXPR.AbsExpression:
123            return "({0} ^ 2) ^ 0.5".format(tmp[0])
124        else:
125            return node._to_string(tmp, None, self.smap, True)
126
127    def visiting_potential_leaf(self, node):
128        """
129        Visiting a potential leaf.
130
131        Return True if the node is not expanded.
132        """
133        #print("ISLEAF")
134        #print(node.__class__)
135        if node is None:
136            return True, None
137
138        if node.__class__ in native_types:
139            return True, ftoa(node)
140
141        if node.is_expression_type():
142            # we will descend into this, so type checking will happen later
143            return False, None
144
145        if node.is_component_type():
146            _ctype = node.ctype
147            if _ctype not in valid_expr_ctypes_minlp:
148                # Make sure all components in active constraints
149                # are basic ctypes we know how to deal with.
150                raise RuntimeError(
151                    "Unallowable component '%s' of type %s found in an active "
152                    "constraint or objective.\nThe GAMS writer cannot export "
153                    "expressions with this component type."
154                    % (node.name, _ctype.__name__))
155
156        if node.is_variable_type():
157            if node.fixed:
158                return True, ftoa(value(node))
159            else:
160                self.variables.add(id(node))
161                label = self.smap.getSymbol(node)
162                return True, label
163
164        return True, ftoa(value(node))
165
166
167def expression_to_string(expr, variables, labeler=None, smap=None):
168    if labeler is not None:
169        if smap is None:
170            smap = SymbolMap()
171        smap.default_labeler = labeler
172    visitor = ToBaronVisitor(variables, smap)
173    return visitor.dfs_postorder_stack(expr)
174
175
176
177# TODO: The to_string function is handy, but the fact that
178#       it calls .name under the hood for all components
179#       everywhere they are used will present ENORMOUS
180#       overhead for components that have a large index set.
181#       It might be worth adding an extra keyword to that
182#       function that takes a "labeler" or "symbol_map" for
183#       writing non-expression components.
184
185@WriterFactory.register('bar', 'Generate the corresponding BARON BAR file.')
186class ProblemWriter_bar(AbstractProblemWriter):
187
188    def __init__(self):
189
190        AbstractProblemWriter.__init__(self, ProblemFormat.bar)
191
192    def _write_equations_section(self,
193                                 model,
194                                 output_file,
195                                 all_blocks_list,
196                                 active_components_data_var,
197                                 symbol_map,
198                                 c_labeler,
199                                 output_fixed_variable_bounds,
200                                 skip_trivial_constraints,
201                                 sorter):
202
203        referenced_variable_ids = OrderedSet()
204
205        def _skip_trivial(constraint_data):
206            if skip_trivial_constraints:
207                if constraint_data._linear_canonical_form:
208                    repn = constraint_data.canonical_form()
209                    if (repn.variables is None) or \
210                       (len(repn.variables) == 0):
211                        return True
212                elif constraint_data.body.polynomial_degree() == 0:
213                    return True
214            return False
215
216        #
217        # Check for active suffixes to export
218        #
219        if isinstance(model, IBlock):
220            suffix_gen = lambda b: ((suf.storage_key, suf) \
221                                    for suf in pyomo.core.kernel.suffix.\
222                                    export_suffix_generator(b,
223                                                            active=True,
224                                                            descend_into=False))
225        else:
226            suffix_gen = lambda b: pyomo.core.base.suffix.\
227                         active_export_suffix_generator(b)
228        r_o_eqns = []
229        c_eqns = []
230        l_eqns = []
231        branching_priorities_suffixes = []
232        for block in all_blocks_list:
233            for name, suffix in suffix_gen(block):
234                if name in {'branching_priorities', 'priority'}:
235                    branching_priorities_suffixes.append(suffix)
236                elif name == 'constraint_types':
237                    for constraint_data, constraint_type in suffix.items():
238                        if not _skip_trivial(constraint_data):
239                            if constraint_type.lower() == 'relaxationonly':
240                                r_o_eqns.append(constraint_data)
241                            elif constraint_type.lower() == 'convex':
242                                c_eqns.append(constraint_data)
243                            elif constraint_type.lower() == 'local':
244                                l_eqns.append(constraint_data)
245                            else:
246                                raise ValueError(
247                                    "A suffix '%s' contained an invalid value: %s\n"
248                                    "Choices are: [relaxationonly, convex, local]"
249                                    % (suffix.name, constraint_type))
250                else:
251                    if block is block.model():
252                        if block.name == 'unknown':
253                            _location = 'model'
254                        else:
255                            _location = "model '%s'" % (block.name,)
256                    else:
257                        _location = "block '%s'" % (block.name,)
258
259                    raise ValueError(
260                        "The BARON writer can not export suffix with name '%s'. "
261                        "Either remove it from the %s or deactivate it."
262                        % (name, _location))
263
264        non_standard_eqns = r_o_eqns + c_eqns + l_eqns
265
266        #
267        # EQUATIONS
268        #
269
270        #Equation Declaration
271        n_roeqns = len(r_o_eqns)
272        n_ceqns = len(c_eqns)
273        n_leqns = len(l_eqns)
274        eqns = []
275
276        # Alias the constraints by declaration order since Baron does not
277        # include the constraint names in the solution file. It is important
278        # that this alias not clash with any real constraint labels, hence
279        # the use of the ".c<integer>" template. It is not possible to declare
280        # a component having this type of name when using standard syntax.
281        # There are ways to do it, but it is unlikely someone will.
282        order_counter = 0
283        alias_template = ".c%d"
284        output_file.write('EQUATIONS ')
285        output_file.write("c_e_FIX_ONE_VAR_CONST__")
286        order_counter += 1
287        for block in all_blocks_list:
288
289            for constraint_data in block.component_data_objects(Constraint,
290                                                                active=True,
291                                                                sort=sorter,
292                                                                descend_into=False):
293
294                if (not constraint_data.has_lb()) and \
295                   (not constraint_data.has_ub()):
296                    assert not constraint_data.equality
297                    continue # non-binding, so skip
298
299                if (not _skip_trivial(constraint_data)) and \
300                   (constraint_data not in non_standard_eqns):
301
302                    eqns.append(constraint_data)
303
304                    con_symbol = symbol_map.createSymbol(constraint_data, c_labeler)
305                    assert not con_symbol.startswith('.')
306                    assert con_symbol != "c_e_FIX_ONE_VAR_CONST__"
307
308                    symbol_map.alias(constraint_data,
309                                      alias_template % order_counter)
310                    output_file.write(", "+str(con_symbol))
311                    order_counter += 1
312
313        output_file.write(";\n\n")
314
315        if n_roeqns > 0:
316            output_file.write('RELAXATION_ONLY_EQUATIONS ')
317            for i, constraint_data in enumerate(r_o_eqns):
318                con_symbol = symbol_map.createSymbol(constraint_data, c_labeler)
319                assert not con_symbol.startswith('.')
320                assert con_symbol != "c_e_FIX_ONE_VAR_CONST__"
321                symbol_map.alias(constraint_data,
322                                  alias_template % order_counter)
323                if i == n_roeqns-1:
324                    output_file.write(str(con_symbol)+';\n\n')
325                else:
326                    output_file.write(str(con_symbol)+', ')
327                order_counter += 1
328
329        if n_ceqns > 0:
330            output_file.write('CONVEX_EQUATIONS ')
331            for i, constraint_data in enumerate(c_eqns):
332                con_symbol = symbol_map.createSymbol(constraint_data, c_labeler)
333                assert not con_symbol.startswith('.')
334                assert con_symbol != "c_e_FIX_ONE_VAR_CONST__"
335                symbol_map.alias(constraint_data,
336                                  alias_template % order_counter)
337                if i == n_ceqns-1:
338                    output_file.write(str(con_symbol)+';\n\n')
339                else:
340                    output_file.write(str(con_symbol)+', ')
341                order_counter += 1
342
343        if n_leqns > 0:
344            output_file.write('LOCAL_EQUATIONS ')
345            for i, constraint_data in enumerate(l_eqns):
346                con_symbol = symbol_map.createSymbol(constraint_data, c_labeler)
347                assert not con_symbol.startswith('.')
348                assert con_symbol != "c_e_FIX_ONE_VAR_CONST__"
349                symbol_map.alias(constraint_data,
350                                  alias_template % order_counter)
351                if i == n_leqns-1:
352                    output_file.write(str(con_symbol)+';\n\n')
353                else:
354                    output_file.write(str(con_symbol)+', ')
355                order_counter += 1
356
357        # Create a dictionary of baron variable names to match to the
358        # strings that constraint.to_string() prints. An important
359        # note is that the variable strings are padded by spaces so
360        # that whole variable names are recognized, and simple
361        # variable names are not identified inside longer names.
362        # Example: ' x[1] ' -> ' x3 '
363        #FIXME: 7/18/14 CLH: This may cause mistakes if spaces in
364        #                    variable names are allowed
365        if isinstance(model, IBlock):
366            mutable_param_gen = lambda b: \
367                                b.components(ctype=Param,
368                                             descend_into=False)
369        else:
370            def mutable_param_gen(b):
371                for param in block.component_objects(Param):
372                    if param.mutable and param.is_indexed():
373                        param_data_iter = \
374                            (param_data for index, param_data
375                             in param.items())
376                    elif not param.is_indexed():
377                        param_data_iter = iter([param])
378                    else:
379                        param_data_iter = iter([])
380
381                    for param_data in param_data_iter:
382                        yield param_data
383
384        if False:
385            #
386            # This was part of a merge from master that caused
387            # test failures.  But commenting this out didn't cause additional failures!?!
388            #
389            vstring_to_var_dict = {}
390            vstring_to_bar_dict = {}
391            pstring_to_bar_dict = {}
392            for block in all_blocks_list:
393                for var_data in active_components_data_var[id(block)]:
394                    variable_stream = StringIO()
395                    var_data.to_string(ostream=variable_stream, verbose=False)
396                    variable_string = variable_stream.getvalue()
397                    variable_string = ' '+variable_string+' '
398                    vstring_to_var_dict[variable_string] = var_data
399                    if output_fixed_variable_bounds or (not var_data.fixed):
400                        vstring_to_bar_dict[variable_string] = \
401                            ' '+object_symbol_dictionary[id(var_data)]+' '
402                    else:
403                        assert var_data.value is not None
404                        vstring_to_bar_dict[variable_string] = \
405                            ftoa(var_data.value)
406
407                for param_data in mutable_param_gen(block):
408                    param_stream = StringIO()
409                    param_data.to_string(ostream=param_stream, verbose=False)
410                    param_string = param_stream.getvalue()
411
412                    param_string = ' '+param_string+' '
413                    pstring_to_bar_dict[param_string] = ftoa(param_data())
414
415        # Equation Definition
416        output_file.write('c_e_FIX_ONE_VAR_CONST__:  ONE_VAR_CONST__  == 1;\n');
417        for constraint_data in itertools.chain(eqns,
418                                               r_o_eqns,
419                                               c_eqns,
420                                               l_eqns):
421
422            variables = OrderedSet()
423            #print(symbol_map.byObject.keys())
424            eqn_body = expression_to_string(constraint_data.body, variables, smap=symbol_map)
425            #print(symbol_map.byObject.keys())
426            referenced_variable_ids.update(variables)
427
428            if len(variables) == 0:
429                assert not skip_trivial_constraints
430                eqn_body += " + 0 * ONE_VAR_CONST__ "
431
432            # 7/29/14 CLH:
433            #FIXME: Baron doesn't handle many of the
434            #       intrinsic_functions available in pyomo. The
435            #       error message given by baron is also very
436            #       weak.  Either a function here to re-write
437            #       unallowed expressions or a way to track solver
438            #       capability by intrinsic_expression would be
439            #       useful.
440            ##########################
441
442            con_symbol = symbol_map.byObject[id(constraint_data)]
443            output_file.write(str(con_symbol) + ': ')
444
445            # Fill in the left and right hand side (constants) of
446            #  the equations
447
448            # Equality constraint
449            if constraint_data.equality:
450                eqn_lhs = ''
451                eqn_rhs = ' == ' + ftoa(constraint_data.upper)
452
453            # Greater than constraint
454            elif not constraint_data.has_ub():
455                eqn_rhs = ' >= ' + ftoa(constraint_data.lower)
456                eqn_lhs = ''
457
458            # Less than constraint
459            elif not constraint_data.has_lb():
460                eqn_rhs = ' <= ' + ftoa(constraint_data.upper)
461                eqn_lhs = ''
462
463            # Double-sided constraint
464            elif constraint_data.has_lb() and \
465                 constraint_data.has_ub():
466                eqn_lhs = ftoa(constraint_data.lower) + \
467                          ' <= '
468                eqn_rhs = ' <= ' + ftoa(constraint_data.upper)
469
470            eqn_string = eqn_lhs + eqn_body + eqn_rhs + ';\n'
471            output_file.write(eqn_string)
472
473        #
474        # OBJECTIVE
475        #
476
477        output_file.write("\nOBJ: ")
478
479        n_objs = 0
480        for block in all_blocks_list:
481
482            for objective_data in block.component_data_objects(Objective,
483                                                               active=True,
484                                                               sort=sorter,
485                                                               descend_into=False):
486
487                n_objs += 1
488                if n_objs > 1:
489                    raise ValueError("The BARON writer has detected multiple active "
490                                     "objective functions on model %s, but "
491                                     "currently only handles a single objective."
492                                     % (model.name))
493
494                # create symbol
495                symbol_map.createSymbol(objective_data, c_labeler)
496                symbol_map.alias(objective_data, "__default_objective__")
497
498                if objective_data.is_minimizing():
499                    output_file.write("minimize ")
500                else:
501                    output_file.write("maximize ")
502
503                variables = OrderedSet()
504                #print(symbol_map.byObject.keys())
505                obj_string = expression_to_string(objective_data.expr, variables, smap=symbol_map)
506                #print(symbol_map.byObject.keys())
507                referenced_variable_ids.update(variables)
508
509
510        output_file.write(obj_string+";\n\n")
511        #referenced_variable_ids.update(symbol_map.byObject.keys())
512
513        return referenced_variable_ids, branching_priorities_suffixes
514
515    def __call__(self,
516                 model,
517                 output_filename,
518                 solver_capability,
519                 io_options):
520        if output_filename is None:
521            output_filename = model.name + ".bar"
522
523        # If the user provides a file name, we will use the opened file
524        # as a context manager to ensure it gets closed.  If the user
525        # provided something else (e.g., a file-like object), we will
526        # use a nullcontext manager to prevent closing it.
527        if isinstance(output_filename, str):
528            output_file = open(output_filename, "w")
529        else:
530            output_file = nullcontext(output_filename)
531
532        with output_file as FILE:
533            symbol_map = self._write_bar_file(
534                model, FILE, solver_capability, io_options)
535
536        return output_filename, symbol_map
537
538
539    def _write_bar_file(self,
540                        model,
541                        output_file,
542                        solver_capability,
543                        io_options):
544        # Make sure not to modify the user's dictionary, they may be
545        # reusing it outside of this call
546        io_options = dict(io_options)
547
548        # NOTE: io_options is a simple dictionary of keyword-value
549        #       pairs specific to this writer.
550        symbolic_solver_labels = \
551            io_options.pop("symbolic_solver_labels", False)
552        labeler = io_options.pop("labeler", None)
553
554        # How much effort do we want to put into ensuring the
555        # LP file is written deterministically for a Pyomo model:
556        #    0 : None
557        #    1 : sort keys of indexed components (default)
558        #    2 : sort keys AND sort names (over declaration order)
559        file_determinism = io_options.pop("file_determinism", 1)
560
561        sorter = SortComponents.unsorted
562        if file_determinism >= 1:
563            sorter = sorter | SortComponents.indices
564            if file_determinism >= 2:
565                sorter = sorter | SortComponents.alphabetical
566
567        output_fixed_variable_bounds = \
568            io_options.pop("output_fixed_variable_bounds", False)
569
570        # Skip writing constraints whose body section is fixed (i.e.,
571        # no variables)
572        skip_trivial_constraints = \
573            io_options.pop("skip_trivial_constraints", False)
574
575        # Note: Baron does not allow specification of runtime
576        #       option outside of this file, so we add support
577        #       for them here
578        solver_options = io_options.pop("solver_options", {})
579
580        if len(io_options):
581            raise ValueError(
582                "ProblemWriter_baron_writer passed unrecognized io_options:\n\t" +
583                "\n\t".join("%s = %s" % (k,v) for k,v in io_options.items()))
584
585        if symbolic_solver_labels and (labeler is not None):
586            raise ValueError("Baron problem writer: Using both the "
587                             "'symbolic_solver_labels' and 'labeler' "
588                             "I/O options is forbidden")
589
590        # Make sure there are no strange ActiveComponents. The expression
591        # walker will handle strange things in constraints later.
592        model_ctypes = model.collect_ctypes(active=True)
593        invalids = set()
594        for t in (model_ctypes - valid_active_ctypes_minlp):
595            if issubclass(t, ActiveComponent):
596                invalids.add(t)
597        if len(invalids):
598            invalids = [t.__name__ for t in invalids]
599            raise RuntimeError(
600                "Unallowable active component(s) %s.\nThe BARON writer cannot "
601                "export models with this component type." %
602                ", ".join(invalids))
603
604        # Process the options. Rely on baron to catch
605        # and reset bad option values
606        output_file.write("OPTIONS {\n")
607        summary_found = False
608        if len(solver_options):
609            for key, val in solver_options.items():
610                if (key.lower() == 'summary'):
611                    summary_found = True
612                if key.endswith("Name"):
613                    output_file.write(key+": \""+str(val)+"\";\n")
614                else:
615                    output_file.write(key+": "+str(val)+";\n")
616        if not summary_found:
617            # The 'summary option is defaulted to 0, so that no
618            # summary file is generated in the directory where the
619            # user calls baron. Check if a user explicitly asked for
620            # a summary file.
621            output_file.write("Summary: 0;\n")
622        output_file.write("}\n\n")
623
624        if symbolic_solver_labels:
625            # Note that the Var and Constraint labelers must use the
626            # same labeler, so that we can correctly detect name
627            # collisions (which can arise when we truncate the labels to
628            # the max allowable length.  BARON requires all identifiers
629            # to start with a letter.  We will (randomly) choose "s_"
630            # (for 'shortened')
631            v_labeler = c_labeler = ShortNameLabeler(
632                15, prefix='s_', suffix='_', caseInsensitive=True,
633                legalRegex='^[a-zA-Z]')
634        elif labeler is None:
635            v_labeler = NumericLabeler('x')
636            c_labeler = NumericLabeler('c')
637        else:
638            v_labeler = c_labeler = labeler
639
640        symbol_map = SymbolMap()
641        symbol_map.default_labeler = v_labeler
642        #sm_bySymbol = symbol_map.bySymbol
643
644        # Cache the list of model blocks so we don't have to call
645        # model.block_data_objects() many many times, which is slow
646        # for indexed blocks
647        all_blocks_list = list(model.block_data_objects(active=True,
648                                                        sort=sorter,
649                                                        descend_into=True))
650        active_components_data_var = {}
651        #for block in all_blocks_list:
652        #    tmp = active_components_data_var[id(block)] = \
653        #          list(obj for obj in block.component_data_objects(Var,
654        #                                                           sort=sorter,
655        #                                                           descend_into=False))
656        #    create_symbols_func(symbol_map, tmp, labeler)
657
658            # GAH: Not sure this is necessary, and also it would break for
659            #      non-mutable indexed params so I am commenting out for now.
660            #for param_data in active_components_data(block, Param, sort=sorter):
661                #instead of checking if param_data.mutable:
662                #if not param_data.is_constant():
663                #    create_symbol_func(symbol_map, param_data, labeler)
664
665        #symbol_map_variable_ids = set(symbol_map.byObject.keys())
666        #object_symbol_dictionary = symbol_map.byObject
667
668        #
669        # Go through the objectives and constraints and generate
670        # the output so that we can obtain the set of referenced
671        # variables.
672        #
673        equation_section_stream = StringIO()
674        referenced_variable_ids, branching_priorities_suffixes = \
675            self._write_equations_section(
676                model,
677                equation_section_stream,
678                all_blocks_list,
679                active_components_data_var,
680                symbol_map,
681                c_labeler,
682                output_fixed_variable_bounds,
683                skip_trivial_constraints,
684                sorter)
685
686        #
687        # BINARY_VARIABLES, INTEGER_VARIABLES, POSITIVE_VARIABLES, VARIABLES
688        #
689
690        BinVars = []
691        IntVars = []
692        PosVars = []
693        Vars = []
694        for vid in referenced_variable_ids:
695            name = symbol_map.byObject[vid]
696            var_data = symbol_map.bySymbol[name]()
697
698            if var_data.is_continuous():
699                if var_data.has_lb() and (value(var_data.lb) >= 0):
700                    TypeList = PosVars
701                else:
702                    TypeList = Vars
703            elif var_data.is_binary():
704                TypeList = BinVars
705            elif var_data.is_integer():
706                TypeList = IntVars
707            else:
708                assert False
709            TypeList.append(name)
710
711        if len(BinVars) > 0:
712            BinVars.sort()
713            output_file.write('BINARY_VARIABLES ')
714            output_file.write(", ".join(BinVars))
715            output_file.write(';\n\n')
716
717        if len(IntVars) > 0:
718            IntVars.sort()
719            output_file.write('INTEGER_VARIABLES ')
720            output_file.write(", ".join(IntVars))
721            output_file.write(';\n\n')
722
723        PosVars.append('ONE_VAR_CONST__')
724        PosVars.sort()
725        output_file.write('POSITIVE_VARIABLES ')
726        output_file.write(", ".join(PosVars))
727        output_file.write(';\n\n')
728
729        if len(Vars) > 0:
730            Vars.sort()
731            output_file.write('VARIABLES ')
732            output_file.write(", ".join(Vars))
733            output_file.write(';\n\n')
734
735        #
736        # LOWER_BOUNDS
737        #
738
739        lbounds = {}
740        for vid in referenced_variable_ids:
741            name = symbol_map.byObject[vid]
742            var_data = symbol_map.bySymbol[name]()
743
744            if var_data.fixed:
745                if output_fixed_variable_bounds:
746                    var_data_lb = ftoa(var_data.value)
747                else:
748                    var_data_lb = None
749            else:
750                var_data_lb = None
751                if var_data.has_lb():
752                    var_data_lb = ftoa(var_data.lb)
753
754            if var_data_lb is not None:
755                name_to_output = symbol_map.getSymbol(var_data)
756                lbounds[name_to_output] = '%s: %s;\n' % (
757                    name_to_output, var_data_lb)
758
759        if len(lbounds) > 0:
760            output_file.write("LOWER_BOUNDS{\n")
761            output_file.write("".join( lbounds[key] for key in sorted(lbounds.keys()) ) )
762            output_file.write("}\n\n")
763        lbounds = None
764
765        #
766        # UPPER_BOUNDS
767        #
768
769        ubounds = {}
770        for vid in referenced_variable_ids:
771            name = symbol_map.byObject[vid]
772            var_data = symbol_map.bySymbol[name]()
773
774            if var_data.fixed:
775                if output_fixed_variable_bounds:
776                    var_data_ub = ftoa(var_data.value)
777                else:
778                    var_data_ub = None
779            else:
780                var_data_ub = None
781                if var_data.has_ub():
782                    var_data_ub = ftoa(var_data.ub)
783
784            if var_data_ub is not None:
785                name_to_output = symbol_map.getSymbol(var_data)
786                ubounds[name_to_output] = '%s: %s;\n' % (
787                    name_to_output, var_data_ub)
788
789        if len(ubounds) > 0:
790            output_file.write("UPPER_BOUNDS{\n")
791            output_file.write("".join( ubounds[key] for key in sorted(ubounds.keys()) ) )
792            output_file.write("}\n\n")
793        ubounds = None
794
795        #
796        # BRANCHING_PRIORITIES
797        #
798
799        # Specifying priorities requires that the pyomo model has established an
800        # EXTERNAL, float suffix called 'branching_priorities' on the model
801        # object, indexed by the relevant variable
802        BranchingPriorityHeader = False
803        for suffix in branching_priorities_suffixes:
804            for var, priority in suffix.items():
805                if var.is_indexed():
806                    var_iter = var.values()
807                else:
808                    var_iter = (var,)
809                for var_data in var_iter:
810                    if id(var_data) not in referenced_variable_ids:
811                        continue
812                    if priority is not None:
813                        if not BranchingPriorityHeader:
814                            output_file.write('BRANCHING_PRIORITIES{\n')
815                            BranchingPriorityHeader = True
816                        output_file.write( "%s: %s;\n" % (
817                            symbol_map.getSymbol(var_data), priority))
818
819        if BranchingPriorityHeader:
820            output_file.write("}\n\n")
821
822        #
823        # Now write the objective and equations section
824        #
825        output_file.write(equation_section_stream.getvalue())
826
827        #
828        # STARTING_POINT
829        #
830        output_file.write('STARTING_POINT{\nONE_VAR_CONST__: 1;\n')
831        tmp = {}
832        for vid in referenced_variable_ids:
833            name = symbol_map.byObject[vid]
834            var_data = symbol_map.bySymbol[name]()
835
836            starting_point = var_data.value
837            if starting_point is not None:
838                var_name = symbol_map.getSymbol(var_data)
839                tmp[var_name] = "%s: %s;\n" % (
840                    var_name, ftoa(starting_point))
841
842        output_file.write("".join( tmp[key] for key in sorted(tmp.keys()) ))
843        output_file.write('}\n\n')
844
845        return symbol_map
846
847