Exemple #1
0
    def test_identify_vars_expr(self):
        #
        # Identify variables when there are duplicates
        #
        m = ConcreteModel()
        m.a = Var(initialize=1)
        m.b = Var(initialize=2)
        m.e = Expression(expr=3*m.a)
        m.E = Expression([0,1], initialize={0:3*m.a, 1:4*m.b})

        self.assertEqual( list(identify_variables(m.b+m.e)), [ m.b, m.a ] )
        self.assertEqual( list(identify_variables(m.E[0])), [ m.a ] )
        self.assertEqual( list(identify_variables(m.E[1])), [ m.b ] )
Exemple #2
0
    def test_identify_variables(self):
        M = ConcreteModel()
        M.x = Var()
        M.y = Var()
        M.w = Var()
        M.w = 2
        M.w.fixed = True

        e = sin(M.x) + M.x*M.w + 3
        v = list(str(v) for v in identify_variables(e))
        self.assertEqual(v, ['x', 'w'])
        v = list(str(v) for v in identify_variables(e, include_fixed=False))
        self.assertEqual(v, ['x'])
Exemple #3
0
    def __init__(self, comp):
        self._vars = ComponentSet()
        self._saved_bounds = list()

        if comp.type() == Constraint:
            if comp.is_indexed():
                for c in comp.values():
                    self._vars.update(identify_variables(c.body))
            else:
                self._vars.update(identify_variables(comp.body))
        else:
            for c in comp.component_data_objects(Constraint, descend_into=True, active=True, sort=True):
                self._vars.update(identify_variables(c.body))
Exemple #4
0
def add_affine_cuts(nlp_result, solve_data, config):
    with time_code(solve_data.timing, "affine cut generation"):
        m = solve_data.linear_GDP
        if config.calc_disjunctive_bounds:
            with time_code(solve_data.timing, "disjunctive variable bounding"):
                TransformationFactory('contrib.compute_disj_var_bounds').apply_to(
                    m,
                    solver=config.mip_solver if config.obbt_disjunctive_bounds else None
                )
        config.logger.info("Adding affine cuts.")
        GDPopt = m.GDPopt_utils
        counter = 0
        for var, val in zip(GDPopt.variable_list, nlp_result.var_values):
            if val is not None and not var.fixed:
                var.value = val

        for constr in constraints_in_True_disjuncts(m, config):
            # Note: this includes constraints that are deactivated in the current model (linear_GDP)

            disjunctive_var_bounds = disjunctive_bounds(constr.parent_block())

            if constr.body.polynomial_degree() in (1, 0):
                continue

            vars_in_constr = list(
                identify_variables(constr.body))
            if any(var.value is None for var in vars_in_constr):
                continue  # a variable has no values

            # mcpp stuff
            mc_eqn = mc(constr.body, disjunctive_var_bounds)
            # mc_eqn = mc(constr.body)
            ccSlope = mc_eqn.subcc()
            cvSlope = mc_eqn.subcv()
            ccStart = mc_eqn.concave()
            cvStart = mc_eqn.convex()
            ub_int = min(constr.upper, mc_eqn.upper()) if constr.has_ub() else mc_eqn.upper()
            lb_int = max(constr.lower, mc_eqn.lower()) if constr.has_lb() else mc_eqn.lower()

            parent_block = constr.parent_block()
            # Create a block on which to put outer approximation cuts.
            aff_utils = parent_block.component('GDPopt_aff')
            if aff_utils is None:
                aff_utils = parent_block.GDPopt_aff = Block(
                    doc="Block holding affine constraints")
                aff_utils.GDPopt_aff_cons = ConstraintList()
            aff_cuts = aff_utils.GDPopt_aff_cons
            concave_cut = sum(ccSlope[var] * (var - var.value)
                              for var in vars_in_constr
                              ) + ccStart >= lb_int
            convex_cut = sum(cvSlope[var] * (var - var.value)
                             for var in vars_in_constr
                             ) + cvStart <= ub_int
            aff_cuts.add(expr=concave_cut)
            aff_cuts.add(expr=convex_cut)
            counter += 2

        config.logger.info("Added %s affine cuts" % counter)
Exemple #5
0
    def test_identify_duplicate_vars(self):
        #
        # Identify variables when there are duplicates
        #
        m = ConcreteModel()
        m.a = Var(initialize=1)

        #self.assertEqual( list(identify_variables(2*m.a+2*m.a, allow_duplicates=True)),
        #                  [ m.a, m.a ] )
        self.assertEqual( list(identify_variables(2*m.a+2*m.a)),
                          [ m.a ] )
Exemple #6
0
 def test_identify_vars_params(self):
     m = ConcreteModel()
     m.I = RangeSet(3)
     m.a = Param(initialize=1)
     m.b = Param(m.I, initialize=1, mutable=True)
     #
     # There are no variables in expressions with only parameters
     #
     self.assertEqual( list(identify_variables(m.a)), [] )
     self.assertEqual( list(identify_variables(m.b[1])), [] )
     self.assertEqual( list(identify_variables(m.a+m.b[1])), [] )
     self.assertEqual( list(identify_variables(m.a**m.b[1])), [] )
     self.assertEqual( list(identify_variables(
         m.a**m.b[1] + m.b[2])), [] )
     self.assertEqual( list(identify_variables(
         m.a**m.b[1] + m.b[2]*m.b[3]*m.b[2])), [] )
Exemple #7
0
 def __init__(self, expression, improved_var_bounds=None):
     super(MCPP_visitor, self).__init__()
     self.mcpp = _MCPP_lib()
     self.missing_value_warnings = []
     self.expr = expression
     vars = list(identify_variables(expression, include_fixed=False))
     self.num_vars = len(vars)
     # Map expression variables to MC variables
     self.known_vars = ComponentMap()
     # Map expression variables to their index
     self.var_to_idx = ComponentMap()
     # Pre-register all variables
     inf = float('inf')
     for i, var in enumerate(vars):
         self.var_to_idx[var] = i
         # check if improved variable bound is provided
         if improved_var_bounds is not None:
             lb, ub = improved_var_bounds.get(var, (-inf, inf))
         else:
             lb, ub = -inf, inf
         self.known_vars[var] = self.register_var(var, lb, ub)
     self.refs = None
Exemple #8
0
 def test_identify_vars_numeric(self):
     #
     # There are no variables in a constant expression
     #
     self.assertEqual(list(identify_variables(5)), [])
Exemple #9
0
 def test_identify_vars_vars(self):
     m = ConcreteModel()
     m.I = RangeSet(3)
     m.a = Var(initialize=1)
     m.b = Var(m.I, initialize=1)
     m.p = Param(initialize=1, mutable=True)
     m.x = ExternalFunction(library='foo.so', function='bar')
     #
     # Identify variables in various algebraic expressions
     #
     self.assertEqual(list(identify_variables(m.a)), [m.a])
     self.assertEqual(list(identify_variables(m.b[1])), [m.b[1]])
     self.assertEqual(list(identify_variables(m.a + m.b[1])), [m.a, m.b[1]])
     self.assertEqual(list(identify_variables(m.a**m.b[1])), [m.a, m.b[1]])
     self.assertEqual(list(identify_variables(m.a**m.b[1] + m.b[2])),
                      [m.a, m.b[1], m.b[2]])
     self.assertEqual(
         list(identify_variables(m.a**m.b[1] + m.b[2] * m.b[3] * m.b[2])),
         [m.a, m.b[1], m.b[2], m.b[3]])
     self.assertEqual(
         list(identify_variables(m.a**m.b[1] + m.b[2] / m.b[3] * m.b[2])),
         [m.a, m.b[1], m.b[2], m.b[3]])
     #
     # Identify variables in the arguments to functions
     #
     self.assertEqual(
         list(identify_variables(m.x(m.a, 'string_param', 1, []) * m.b[1])),
         [m.a, m.b[1]])
     self.assertEqual(
         list(identify_variables(m.x(m.p, 'string_param', 1, []) * m.b[1])),
         [m.b[1]])
     self.assertEqual(list(identify_variables(tanh(m.a) * m.b[1])),
                      [m.a, m.b[1]])
     self.assertEqual(list(identify_variables(abs(m.a) * m.b[1])),
                      [m.a, m.b[1]])
     #
     # Check logic for allowing duplicates
     #
     self.assertEqual(list(identify_variables(m.a**m.a + m.a)), [m.a])
Exemple #10
0
 def test_identify_vars_linear_expression(self):
     m = ConcreteModel()
     m.x = Var()
     expr = quicksum([m.x, m.x], linear=True)
     self.assertEqual(list(identify_variables(expr, include_fixed=False)),
                      [m.x])
Exemple #11
0
def solve_strongly_connected_components(
    block,
    solver=None,
    solve_kwds=None,
    calc_var_kwds=None,
):
    """ This function solves a square block of variables and equality
    constraints by solving strongly connected components individually.
    Strongly connected components (of the directed graph of constraints
    obtained from a perfect matching of variables and constraints) are
    the diagonal blocks in a block triangularization of the incidence
    matrix, so solving the strongly connected components in topological
    order is sufficient to solve the entire block.

    One-by-one blocks are solved using Pyomo's
    calculate_variable_from_constraint function, while higher-dimension
    blocks are solved using the user-provided solver object.

    Arguments
    ---------
    block: Pyomo Block
        The Pyomo block whose variables and constraints will be solved
    solver: Pyomo solver object
        The solver object that will be used to solve strongly connected
        components of size greater than one constraint. Must implement
        a solve method.
    solve_kwds: Dictionary
        Keyword arguments for the solver's solve method
    calc_var_kwds: Dictionary
        Keyword arguments for calculate_variable_from_constraint

    Returns
    -------
    List of results objects returned by each call to solve

    """
    if solve_kwds is None:
        solve_kwds = {}
    if calc_var_kwds is None:
        calc_var_kwds = {}

    constraints = list(block.component_data_objects(Constraint, active=True))
    var_set = ComponentSet()
    variables = []
    for con in constraints:
        for var in identify_variables(con.expr, include_fixed=False):
            # Because we are solving, we do not want to include fixed variables
            if var not in var_set:
                variables.append(var)
                var_set.add(var)

    res_list = []
    for scc, inputs in generate_strongly_connected_components(
            constraints,
            variables,
    ):
        with TemporarySubsystemManager(to_fix=inputs):
            if len(scc.vars) == 1:
                results = calculate_variable_from_constraint(
                    scc.vars[0], scc.cons[0], **calc_var_kwds)
                res_list.append(results)
            else:
                if solver is None:
                    # NOTE: Use local name to avoid slow generation of this
                    # error message if a user provides a large, non-decomposable
                    # block with no solver.
                    vars = [var.local_name for var in scc.vars.values()]
                    cons = [con.local_name for con in scc.cons.values()]
                    raise RuntimeError(
                        "An external solver is required if block has strongly\n"
                        "connected components of size greater than one (is not "
                        "a DAG).\nGot an SCC with components: \n%s\n%s" %
                        (vars, cons))
                results = solver.solve(scc, **solve_kwds)
                res_list.append(results)
    return res_list
Exemple #12
0
def coefficient_matching(model, constraint, uncertain_params, config):
    '''
    :param model: master problem model
    :param constraint: the constraint from the master problem model
    :param uncertain_params: the list of uncertain parameters
    :param first_stage_variables: the list of effective first-stage variables (includes ssv if decision_rule_order = 0)
    :return: True if the coefficient matching was successful, False if its proven robust_infeasible due to
             constraints of the form 1 == 0
    '''
    # === Returned flags
    successful_matching = True
    robust_infeasible = False

    # === Efficiency for q_LB = q_UB
    actual_uncertain_params = []

    for i in range(len(uncertain_params)):
        if not is_certain_parameter(uncertain_param_index=i, config=config):
            actual_uncertain_params.append(uncertain_params[i])

    # === Add coefficient matching constraint list
    if not hasattr(model, "coefficient_matching_constraints"):
        model.coefficient_matching_constraints = ConstraintList()
    if not hasattr(model, "swapped_constraints"):
        model.swapped_constraints = ConstraintList()

    variables_in_constraint = ComponentSet(identify_variables(constraint.expr))
    params_in_constraint = ComponentSet(identify_mutable_parameters(constraint.expr))
    first_stage_variables = model.util.first_stage_variables
    second_stage_variables = model.util.second_stage_variables

    # === Determine if we need to do DR expression/ssv substitution to
    #     make h(x,z,q) == 0 into h(x,d,q) == 0 (which is just h(x,q) == 0)
    if all(v in ComponentSet(first_stage_variables) for v in variables_in_constraint) and \
            any(q in ComponentSet(actual_uncertain_params) for q in params_in_constraint):
        # h(x, q) == 0
        pass
    elif all(v in ComponentSet(first_stage_variables + second_stage_variables) for v in variables_in_constraint) and \
            any(q in ComponentSet(actual_uncertain_params) for q in params_in_constraint):
        constraint = substitute_ssv_in_dr_constraints(model=model, constraint=constraint)
        variables_in_constraint = ComponentSet(identify_variables(constraint.expr))
        params_in_constraint = ComponentSet(identify_mutable_parameters(constraint.expr))
    else:
        pass

    if all(v in ComponentSet(first_stage_variables) for v in variables_in_constraint) and \
            any(q in ComponentSet(actual_uncertain_params) for q in params_in_constraint):

        # Swap param objects for variable objects in this constraint
        model.param_set = []
        for i in range(len(list(variables_in_constraint))):
            # Initialize Params to non-zero value due to standard_repn bug
            model.add_component("p_%s" % i, Param(initialize=1, mutable=True))
            model.param_set.append(getattr(model, "p_%s" % i))

        model.variable_set = []
        for i in range(len(list(actual_uncertain_params))):
            model.add_component("x_%s" % i, Var(initialize=1))
            model.variable_set.append(getattr(model, "x_%s" % i))

        original_var_to_param_map = list(zip(list(variables_in_constraint), model.param_set))
        original_param_to_vap_map = list(zip(list(actual_uncertain_params), model.variable_set))

        var_to_param_substitution_map_forward = {}
        # Separation problem initialized to nominal uncertain parameter values
        for var, param in original_var_to_param_map:
            var_to_param_substitution_map_forward[id(var)] = param

        param_to_var_substitution_map_forward = {}
        # Separation problem initialized to nominal uncertain parameter values
        for param, var in original_param_to_vap_map:
            param_to_var_substitution_map_forward[id(param)] = var

        var_to_param_substitution_map_reverse = {}
        # Separation problem initialized to nominal uncertain parameter values
        for var, param in original_var_to_param_map:
            var_to_param_substitution_map_reverse[id(param)] = var

        param_to_var_substitution_map_reverse = {}
        # Separation problem initialized to nominal uncertain parameter values
        for param, var in original_param_to_vap_map:
            param_to_var_substitution_map_reverse[id(var)] = param

        model.swapped_constraints.add(
            replace_expressions(
                expr=replace_expressions(expr=constraint.lower,
                                         substitution_map=param_to_var_substitution_map_forward),
                substitution_map=var_to_param_substitution_map_forward) ==
            replace_expressions(
                expr=replace_expressions(expr=constraint.body,
                                         substitution_map=param_to_var_substitution_map_forward),
                substitution_map=var_to_param_substitution_map_forward))

        swapped = model.swapped_constraints[max(model.swapped_constraints.keys())]

        val = generate_standard_repn(swapped.body, compute_values=False)

        if val.constant is not None:
            if type(val.constant) not in native_types:
                temp_expr = replace_expressions(val.constant, substitution_map=var_to_param_substitution_map_reverse)
                if temp_expr.is_potentially_variable():
                    model.coefficient_matching_constraints.add(expr=temp_expr == 0)
                elif math.isclose(value(temp_expr), 0, rel_tol=COEFF_MATCH_REL_TOL, abs_tol=COEFF_MATCH_ABS_TOL):
                    pass
                else:
                    successful_matching = False
                    robust_infeasible = True
            elif math.isclose(value(val.constant), 0, rel_tol=COEFF_MATCH_REL_TOL, abs_tol=COEFF_MATCH_ABS_TOL):
                pass
            else:
                successful_matching = False
                robust_infeasible = True
        if val.linear_coefs is not None:
            for coeff in val.linear_coefs:
                if type(coeff) not in native_types:
                    temp_expr = replace_expressions(coeff, substitution_map=var_to_param_substitution_map_reverse)
                    if temp_expr.is_potentially_variable():
                        model.coefficient_matching_constraints.add(expr=temp_expr == 0)
                    elif math.isclose(value(temp_expr), 0, rel_tol=COEFF_MATCH_REL_TOL, abs_tol=COEFF_MATCH_ABS_TOL):
                        pass
                    else:
                        successful_matching = False
                        robust_infeasible = True
                elif math.isclose(value(coeff), 0, rel_tol=COEFF_MATCH_REL_TOL, abs_tol=COEFF_MATCH_ABS_TOL):
                    pass
                else:
                    successful_matching = False
                    robust_infeasible = True
        if val.quadratic_coefs:
            for coeff in val.quadratic_coefs:
                if type(coeff) not in native_types:
                    temp_expr = replace_expressions(coeff, substitution_map=var_to_param_substitution_map_reverse)
                    if temp_expr.is_potentially_variable():
                        model.coefficient_matching_constraints.add(expr=temp_expr == 0)
                    elif math.isclose(value(temp_expr), 0, rel_tol=COEFF_MATCH_REL_TOL, abs_tol=COEFF_MATCH_ABS_TOL):
                        pass
                    else:
                        successful_matching = False
                        robust_infeasible = True
                elif math.isclose(value(coeff), 0, rel_tol=COEFF_MATCH_REL_TOL, abs_tol=COEFF_MATCH_ABS_TOL):
                    pass
                else:
                    successful_matching = False
                    robust_infeasible = True
        if val.nonlinear_expr is not None:
            successful_matching = False
            robust_infeasible = False

        if successful_matching:
            model.util.h_x_q_constraints.add(constraint)

    for i in range(len(list(variables_in_constraint))):
        model.del_component("p_%s" % i)

    for i in range(len(list(params_in_constraint))):
        model.del_component("x_%s" % i)

    model.del_component("swapped_constraints")
    model.del_component("swapped_constraints_index")

    return successful_matching, robust_infeasible
Exemple #13
0
 def test_identify_vars_numeric(self):
     #
     # There are no variables in a constant expression
     #
     self.assertEqual( list(identify_variables(5)), [] )
Exemple #14
0
def generate_strongly_connected_components(
    constraints,
    variables=None,
    include_fixed=False,
):
    """ Performs a block triangularization of the incidence matrix
    of the provided constraints and variables, and yields a block that
    contains the constraints and variables of each diagonal block
    (strongly connected component).

    Arguments
    ---------
    constraints: List of Pyomo constraint data objects
        Constraints used to generate strongly connected components.
    variables: List of Pyomo variable data objects
        Variables that may participate in strongly connected components.
        If not provided, all variables in the constraints will be used.
    include_fixed: Bool
        Indicates whether fixed variables will be included when
        identifying variables in constraints.

    Yields
    ------
    Blocks containing the variables and constraints of every strongly
    connected component, in a topological order, as well as the
    "input variables" for that block

    """
    if variables is None:
        var_set = ComponentSet()
        variables = []
        for con in constraints:
            for var in identify_variables(
                    con.expr,
                    include_fixed=include_fixed,
            ):
                if var not in var_set:
                    variables.append(var)
                    var_set.add(var)

    assert len(variables) == len(constraints)
    igraph = IncidenceGraphInterface()
    var_block_map, con_block_map = igraph.block_triangularize(
        variables=variables,
        constraints=constraints,
    )
    blocks = set(var_block_map.values())
    n_blocks = len(blocks)
    var_blocks = [[] for b in range(n_blocks)]
    con_blocks = [[] for b in range(n_blocks)]
    for var, b in var_block_map.items():
        var_blocks[b].append(var)
    for con, b in con_block_map.items():
        con_blocks[b].append(con)
    subsets = list(zip(con_blocks, var_blocks))
    for block, inputs in generate_subsystem_blocks(
            subsets,
            include_fixed=include_fixed,
    ):
        # TODO: How does len scale for reference-to-list?
        assert len(block.vars) == len(block.cons)
        yield (block, inputs)
    def test_init_indexed(self):
        block_set = pyo.Set(initialize=[0, 1, 2])
        block_set.construct()
        horizon_map = {0: 1., 1: 3., 2: 5.}
        nfe_map = {0: 2, 1: 6, 2: 10}
        model_map = {
            i: make_model(horizon_map[i], nfe_map[i])
            for i in block_set
        }
        time_map = {i: model_map[i].time for i in block_set}
        inputs_map = {i: [model_map[i].flow_in[0]] for i in block_set}
        measurements_map = {
            i: [model_map[i].conc[0, 'A'], model_map[i].conc[0, 'B']]
            for i in block_set
        }
        # Construct block with a dict for each of its arguments
        block = DynamicBlock(
            block_set,
            model=model_map,
            time=time_map,
            inputs=inputs_map,
            measurements=measurements_map,
        )
        # Make sure we have the right type
        assert type(block) is IndexedDynamicBlock
        assert isinstance(block, DynamicBlock)

        block.construct()
        assert all(b.parent_component() is block for b in block.values())

        # Check __contains__
        for i in block_set:
            assert i in block

        # Check attributes and subblocks of each data object
        for i, b in block.items():
            assert b.mod is model_map[i]
            assert b.time is time_map[i]
            assert all(i1 is i2 for i1, i2 in zip(b._inputs, inputs_map[i]))
            assert all(i1 is i2
                       for i1, i2 in zip(b._measurements, measurements_map[i]))

            assert hasattr(b, 'category_dict')
            assert hasattr(b, 'vardata_map')
            assert hasattr(b, 'measurement_vars')
            assert hasattr(b, 'differential_vars')
            assert hasattr(b, 'algebraic_vars')
            assert hasattr(b, 'derivative_vars')
            assert hasattr(b, 'input_vars')
            assert hasattr(b, 'fixed_vars')

            subblocks = [
                b.mod,
                b.vectors,
                b.DIFFERENTIAL_BLOCK,
                b.ALGEBRAIC_BLOCK,
                b.INPUT_BLOCK,
                b.FIXED_BLOCK,
                b.DERIVATIVE_BLOCK,
                b.MEASUREMENT_BLOCK,
            ]

            block_objects = ComponentSet(
                b.component_objects(pyo.Block, descend_into=False))
            assert len(subblocks) == len(block_objects)
            for sb in subblocks:
                assert sb in block_objects

            b.v = pyo.Var(initialize=3)
            b.c = pyo.Constraint(expr=b.v == 5)
            assert b.v.value == 3
            assert b.v in ComponentSet(identify_variables(b.c.expr))
Exemple #16
0
def initialize_by_time_element(fs, time, **kwargs):
    """
    Function to initialize Flowsheet fs element-by-element along 
    ContinuousSet time. Assumes sufficient initialization/correct degrees 
    of freedom such that the first finite element can be solved immediately 
    and each subsequent finite element can be solved by fixing differential
    and derivative variables at the initial time point of that finite element.

    Args:
        fs : Flowsheet to initialize
        time : Set whose elements will be solved for individually
        solver : Pyomo solver object initialized with user's desired options
        outlvl : IDAES logger outlvl
        ignore_dof : Bool. If True, checks for square problems will be skipped.

    Returns:
        None
    """
    if not isinstance(fs, FlowsheetBlock):
        raise TypeError('First arg must be a FlowsheetBlock')
    if not isinstance(time, ContinuousSet):
        raise TypeError('Second arg must be a ContinuousSet')

    if time.get_discretization_info() == {}:
        raise ValueError('ContinuousSet must be discretized')

    scheme = time.get_discretization_info()['scheme']
    fep_list = time.get_finite_elements()
    nfe = time.get_discretization_info()['nfe']

    if scheme == 'LAGRANGE-RADAU':
        ncp = time.get_discretization_info()['ncp']
    elif scheme == 'LAGRANGE-LEGENDRE':
        msg = 'Initialization does not support collocation with Legendre roots'
        raise NotImplementedError(msg)
    elif scheme == 'BACKWARD Difference':
        ncp = 1
    elif scheme == 'FORWARD Difference':
        ncp = 1
        msg = 'Forward initialization (explicit Euler) has not yet been implemented'
        raise NotImplementedError(msg)
    elif scheme == 'CENTRAL Difference':
        msg = 'Initialization does not support central finite difference'
        raise NotImplementedError(msg)
    else:
        msg = 'Unrecognized discretization scheme. '
        'Has the model been discretized along the provided ContinuousSet?'
        raise ValueError(msg)
    # Disallow Central/Legendre discretizations.
    # Neither of these seem to be square by default for multi-finite element
    # initial value problems.

    # Create logger objects
    outlvl = kwargs.pop('outlvl', idaeslog.NOTSET)
    init_log = idaeslog.getInitLogger(__name__, level=outlvl)
    solver_log = idaeslog.getSolveLogger(__name__, level=outlvl)

    ignore_dof = kwargs.pop('ignore_dof', False)
    solver = kwargs.pop('solver', SolverFactory('ipopt'))
    fix_diff_only = kwargs.pop('fix_diff_only', True)
    # This option makes the assumption that the only variables that
    # link constraints to previous points in time (which must be fixed)
    # are the derivatives and differential variables. Not true if a controller
    # is being present, but should be a good assumption otherwise, and is
    # significantly faster than searching each constraint for time-linking
    # variables.

    if not ignore_dof:
        if degrees_of_freedom(fs) != 0:
            msg = ('Original model has nonzero degrees of freedom. This was '
                   'unexpected. Use keyword arg igore_dof=True to skip this '
                   'check.')
            init_log.error(msg)
            raise ValueError('Nonzero degrees of freedom.')

    # Get dict telling which constraints/blocks are already inactive:
    # dict: id(compdata) -> bool (is active?)
    was_originally_active = get_activity_dict(fs)

    # Deactivate flowsheet except at t0, solve to ensure consistency
    # of initial conditions.
    non_initial_time = [t for t in time]
    non_initial_time.remove(time.first())
    deactivated = deactivate_model_at(fs,
                                      time,
                                      non_initial_time,
                                      outlvl=idaeslog.ERROR)

    if not ignore_dof:
        if degrees_of_freedom(fs) != 0:
            msg = (
                'Model has nonzero degrees of freedom at initial conditions.'
                ' This was unexpected. Use keyword arg igore_dof=True to skip'
                ' this check.')
            init_log.error(msg)
            raise ValueError('Nonzero degrees of freedom.')

    init_log.info(
        'Model is inactive except at t=0. Solving for consistent initial conditions.'
    )
    with idaeslog.solver_log(solver_log, level=idaeslog.DEBUG) as slc:
        results = solver.solve(fs, tee=slc.tee)
    if results.solver.termination_condition == TerminationCondition.optimal:
        init_log.info('Successfully solved for consistent initial conditions')
    else:
        init_log.error('Failed to solve for consistent initial conditions')
        raise ValueError('Solver failed in initialization')

    deactivated[time.first()] = deactivate_model_at(
        fs, time, time.first(), outlvl=idaeslog.ERROR)[time.first()]

    # Here, deactivate non-time-indexed components. Do this after solve
    # for initial conditions in case these were used to specify initial
    # conditions
    con_unindexed_by_time = deactivate_constraints_unindexed_by(fs, time)
    var_unindexed_by_time = fix_vars_unindexed_by(fs, time)

    # Now model is completely inactive

    # For each timestep, we need to
    # 1. Activate model at points we're solving for
    # 2. Fix initial conditions (differential variables at previous timestep)
    #    of finite element
    # 3. Solve the (now) square system
    # 4. Revert the model to its prior state

    # This will make use of the following dictionaries mapping
    # time points -> time derivatives and time-differential variables
    derivs_at_time = get_derivatives_at(fs, time, [t for t in time])
    dvars_at_time = {
        t: [
            d.parent_component().get_state_var()[d.index()]
            for d in derivs_at_time[t]
        ]
        for t in time
    }

    # Perform a solve for 1 -> nfe; i is the index of the finite element
    init_log.info(
        'Flowsheet has been deactivated. Beginning element-wise initialization'
    )
    for i in range(1, nfe + 1):
        t_prev = time[(i - 1) * ncp + 1]
        # Non-initial time points in the finite element:
        fe = [time[k] for k in range((i - 1) * ncp + 2, i * ncp + 2)]

        init_log.info(f'Entering step {i}/{nfe} of initialization')

        # Activate components of model that were active in the presumably
        # square original system
        for t in fe:
            for comp in deactivated[t]:
                if was_originally_active[id(comp)]:
                    comp.activate()

        # Get lists of derivative and differential variables
        # at initial time point of finite element
        init_deriv_list = derivs_at_time[t_prev]
        init_dvar_list = dvars_at_time[t_prev]

        # Variables that were originally fixed
        fixed_vars = []
        if fix_diff_only:
            for drv in init_deriv_list:
                # Cannot fix variables with value None.
                # Any variable with value None was not solved for
                # (either stale or not included in previous solve)
                # and we don't want to fix it.
                if not drv.fixed:
                    fixed_vars.append(drv)
                if not drv.value is None:
                    drv.fix()
            for dv in init_dvar_list:
                if not dv.fixed:
                    fixed_vars.append(dv)
                if not dv.value is None:
                    dv.fix()
        else:
            for con in fs.component_data_objects(Constraint, active=True):
                for var in identify_variables(con.expr, include_fixed=False):
                    t_idx = get_implicit_index_of_set(var, time)
                    if t_idx is None:
                        continue
                    if t_idx <= t_prev:
                        fixed_vars.append(var)
                        var.fix()

        # Initialize finite element from its initial conditions
        for t in fe:
            copy_values_at_time(fs,
                                fs,
                                t,
                                t_prev,
                                copy_fixed=False,
                                outlvl=idaeslog.ERROR)

        # Log that we are solving finite element {i}
        init_log.info(f'Solving finite element {i}')

        if not ignore_dof:
            if degrees_of_freedom(fs) != 0:
                msg = (
                    f'Model has nonzero degrees of freedom at finite element'
                    ' {i}. This was unexpected. '
                    'Use keyword arg igore_dof=True to skip this check.')
                init_log.error(msg)
                raise ValueError('Nonzero degrees of freedom')

        with idaeslog.solver_log(solver_log, level=idaeslog.DEBUG) as slc:
            results = solver.solve(fs, tee=slc.tee)
        if results.solver.termination_condition == TerminationCondition.optimal:
            init_log.info(f'Successfully solved finite element {i}')
        else:
            init_log.error(f'Failed to solve finite element {i}')
            raise ValueError('Failure in initialization solve')

        # Deactivate components that may have been activated
        for t in fe:
            for comp in deactivated[t]:
                comp.deactivate()

        # Unfix variables that have been fixed
        for var in fixed_vars:
            var.unfix()

        # Log that initialization step {i} has been finished
        init_log.info(f'Initialization step {i} complete')

    # Reactivate components of the model that were originally active
    for t in time:
        for comp in deactivated[t]:
            if was_originally_active[id(comp)]:
                comp.activate()

    for con in con_unindexed_by_time:
        con.activate()
    for var in var_unindexed_by_time:
        var.unfix()

    # Logger message that initialization is finished
    init_log.info('Initialization completed. Model has been reactivated')
Exemple #17
0
def fbbt_block(m, tol=1e-4, deactivate_satisfied_constraints=False, integer_tol=1e-5, infeasible_tol=1e-8):
    """
    Feasibility based bounds tightening (FBBT) for a block or model. This
    loops through all of the constraints in the block and performs
    FBBT on each constraint (see the docstring for fbbt_con()).
    Through this processes, any variables whose bounds improve
    by more than tol are collected, and FBBT is
    performed again on all constraints involving those variables.
    This process is continued until no variable bounds are improved
    by more than tol.

    Parameters
    ----------
    m: pyomo.core.base.block.Block or pyomo.core.base.PyomoModel.ConcreteModel
    tol: float
    deactivate_satisfied_constraints: bool
        If deactivate_satisfied_constraints is True and a constraint is always satisfied, then the constranit
        will be deactivated
    integer_tol: float
        If the lower bound computed on a binary variable is less than or equal to integer_tol, then the
        lower bound is left at 0. Otherwise, the lower bound is increased to 1. If the upper bound computed
        on a binary variable is greater than or equal to 1-integer_tol, then the upper bound is left at 1.
        Otherwise the upper bound is decreased to 0.
    infeasible_tol: float
        If the bounds computed on the body of a constraint violate the bounds of the constraint by more than
        infeasible_tol, then the constraint is considered infeasible and an exception is raised.

    Returns
    -------
    new_var_bounds: ComponentMap
        A ComponentMap mapping from variables a tuple containing the lower and upper bounds, respectively, computed
        from FBBT.
    """
    new_var_bounds = ComponentMap()
    var_to_con_map = ComponentMap()
    var_lbs = ComponentMap()
    var_ubs = ComponentMap()
    for c in m.component_data_objects(ctype=Constraint, active=True,
                                      descend_into=True, sort=True):
        for v in identify_variables(c.body):
            if v not in var_to_con_map:
                var_to_con_map[v] = list()
            if v.lb is None:
                var_lbs[v] = -math.inf
            else:
                var_lbs[v] = value(v.lb)
            if v.ub is None:
                var_ubs[v] = math.inf
            else:
                var_ubs[v] = value(v.ub)
            var_to_con_map[v].append(c)

    for _v in m.component_data_objects(ctype=Var, active=True, descend_into=True, sort=True):
        if _v.is_fixed():
            _v.setlb(_v.value)
            _v.setub(_v.value)
            new_var_bounds[_v] = (_v.value, _v.value)

    improved_vars = ComponentSet()
    for c in m.component_data_objects(ctype=Constraint, active=True,
                                      descend_into=True, sort=True):
        _new_var_bounds = fbbt_con(c, deactivate_satisfied_constraints=deactivate_satisfied_constraints,
                                   integer_tol=integer_tol, infeasible_tol=infeasible_tol)
        new_var_bounds.update(_new_var_bounds)
        for v, bnds in _new_var_bounds.items():
            vlb, vub = bnds
            if vlb is not None:
                if vlb > var_lbs[v] + tol:
                    improved_vars.add(v)
                    var_lbs[v] = vlb
            if vub is not None:
                if vub < var_ubs[v] - tol:
                    improved_vars.add(v)
                    var_ubs[v] = vub

    while len(improved_vars) > 0:
        v = improved_vars.pop()
        for c in var_to_con_map[v]:
            _new_var_bounds = fbbt_con(c, deactivate_satisfied_constraints=deactivate_satisfied_constraints,
                                       integer_tol=integer_tol, infeasible_tol=infeasible_tol)
            new_var_bounds.update(_new_var_bounds)
            for _v, bnds in _new_var_bounds.items():
                _vlb, _vub = bnds
                if _vlb is not None:
                    if _vlb > var_lbs[_v] + tol:
                        improved_vars.add(_v)
                        var_lbs[_v] = _vlb
                if _vub is not None:
                    if _vub < var_ubs[_v] - tol:
                        improved_vars.add(_v)
                        var_ubs[_v] = _vub

    return new_var_bounds
    def test_init_rule(self):
        block_set = pyo.Set(initialize=range(3))
        block_set.construct()
        # Create same maps as before
        horizon_map = {0: 1, 1: 3, 2: 5}
        nfe_map = {0: 2, 1: 6, 2: 10}
        model_map = {
            i: make_model(horizon=horizon_map[i], nfe=nfe_map[i])
            for i in block_set
        }

        # Create rule to construct DynamicBlock with
        def dynamic_block_rule(b, i):
            model = model_map[i]
            time = model.time
            t0 = time.first()
            inputs = [model.flow_in[t0]]
            measurements = [model.conc[0, 'A'], model.conc[0, 'B']]

            # Won't be obvious that these attrs need to be set if
            # constructing from a rule
            b.mod = model
            super(_BlockData, b).__setattr__('time', time)
            b._inputs = inputs
            b._measurements = measurements

        # Create DynamicBlock from a rule
        block = DynamicBlock(block_set, rule=dynamic_block_rule)
        assert type(block) is IndexedDynamicBlock
        assert isinstance(block, DynamicBlock)

        block.construct()

        # Make sure iterating over block.values works as expected
        assert all(b.parent_component() is block for b in block.values())

        # Make sure __contains__ works
        for i in block_set:
            assert i in block

        # Assert correct attributes and subblocks
        for i, b in block.items():
            assert b.mod is model_map[i]
            assert b.time is model_map[i].time
            t0 = b.time.first()
            assert all(
                i1 is i2
                for i1, i2 in zip(b._inputs, [model_map[i].flow_in[t0]]))
            assert all(i1 is i2 for i1, i2 in zip(
                b._measurements,
                [model_map[i].conc[t0, 'A'], model_map[i].conc[t0, 'B']]))

            assert hasattr(b, 'category_dict')
            assert hasattr(b, 'vardata_map')
            assert hasattr(b, 'measurement_vars')
            assert hasattr(b, 'differential_vars')
            assert hasattr(b, 'algebraic_vars')
            assert hasattr(b, 'derivative_vars')
            assert hasattr(b, 'input_vars')
            assert hasattr(b, 'fixed_vars')

            subblocks = [
                b.mod,
                b.vectors,
                b.DIFFERENTIAL_BLOCK,
                b.ALGEBRAIC_BLOCK,
                b.INPUT_BLOCK,
                b.FIXED_BLOCK,
                b.DERIVATIVE_BLOCK,
                b.MEASUREMENT_BLOCK,
            ]

            block_objects = ComponentSet(
                b.component_objects(pyo.Block, descend_into=False))
            assert len(subblocks) == len(block_objects)
            for sb in subblocks:
                assert sb in block_objects

            b.v = pyo.Var(initialize=3)
            b.c = pyo.Constraint(expr=b.v == 5)
            assert b.v.value == 3
            assert b.v in ComponentSet(identify_variables(b.c.expr))
    def test_init_simple(self):
        model = make_model(horizon=1, nfe=2)
        time = model.time
        t0 = time.first()
        inputs = [model.flow_in[t0]]
        measurements = [model.conc[0, 'A'], model.conc[0, 'B']]
        block = DynamicBlock(
            model=model,
            time=time,
            inputs=inputs,
            measurements=measurements,
        )
        # Assert that we have the correct type
        assert type(block) is SimpleDynamicBlock
        assert isinstance(block, DynamicBlock)
        assert isinstance(block, _DynamicBlockData)

        block.construct()
        # Assert that we behave like a simple block
        assert block[None] is block
        assert all(b is block for b in block[:])

        # Assert that input attributes have been processed correctly
        assert block.mod is model
        assert block.time is time
        assert all(i1 is i2 for i1, i2 in zip(block._inputs, inputs))
        assert all(i1 is i2
                   for i1, i2 in zip(block._measurements, measurements))

        # Assert that utility attributes have been added
        assert hasattr(block, 'category_dict')
        assert hasattr(block, 'vardata_map')
        assert hasattr(block, 'measurement_vars')
        assert hasattr(block, 'differential_vars')
        assert hasattr(block, 'algebraic_vars')
        assert hasattr(block, 'derivative_vars')
        assert hasattr(block, 'input_vars')
        assert hasattr(block, 'fixed_vars')

        subblocks = [
            block.mod,
            block.vectors,
            block.DIFFERENTIAL_BLOCK,
            block.ALGEBRAIC_BLOCK,
            block.INPUT_BLOCK,
            block.FIXED_BLOCK,
            block.DERIVATIVE_BLOCK,
            block.MEASUREMENT_BLOCK,
        ]

        block_objects = ComponentSet(
            block.component_objects(pyo.Block, descend_into=False))
        # Assert that subblocks have been added
        assert len(subblocks) == len(block_objects)
        for b in subblocks:
            assert b in block_objects

        # Assert that we can add variables and constraints to the block
        block.v = pyo.Var(initialize=3)
        block.c = pyo.Constraint(expr=block.v == 5)
        assert block.v.value == 3
        assert block.v in ComponentSet(identify_variables(block.c.expr))
Exemple #20
0
def create_linear_dual_from(block, fixed=None, unfixed=None):
    """
    Construct a block that represents the dual of the given block.

    The resulting block contains variables and constraints whose names are
    the dual names of the primal block.  Note that this involves a many
    string operations.  A quicker operations could be executed, but it
    would generate a dual representation that is difficult to interpret.

    Note that the dualization of a maximization problem is performed by
    negating objective and right-hand side coefficients after dualizing
    the corresponding minimization problem.  This suggestion is made
    by Dimitri Bertsimas and John Tsitsiklis in section 4.2 page 143 of
    "Introduction to Linear Optimization"

    Arguments:
        block: A Pyomo block or model
        unfixed: An iterable object with Variable and VarData values that are 
                not fixed variables.  All other variables are assumed to be fixed.
        fixed: An iterable object with Variable and VarData values that are fixed.  
                All other variables are assumed not fixed.

    Returns:
        If the block is a model object, then this returns a ConcreteModel.
        Otherwise, it returns a Block.
    """
    #
    # Collect vardata that needs to be fixed
    #
    fixed_modelvars = {}
    if fixed or unfixed:
        #
        # Collect model variables
        #
        modelvars = {}
        #
        # vardata in objectives
        #
        for obj in block.component_objects(Objective, active=True):
            for ndx in obj:
                #odata = generate_standard_repn(obj[ndx].expr, compute_values=False)
                for vdata in identify_variables(obj[ndx].expr,
                                                include_fixed=False):
                    id_ = id(vdata)
                    if not id_ in modelvars:
                        modelvars[id_] = vdata
        #
        # vardata in constraints
        #
        for con in block.component_objects(Constraint, active=True):
            for ndx in con:
                #cdata = generate_standard_repn(con[ndx].body, compute_values=False)
                for vdata in identify_variables(con[ndx].body,
                                                include_fixed=False):
                    id_ = id(vdata)
                    if not id_ in modelvars:
                        modelvars[id_] = vdata
        #
        # Fix everything that isn't specified as unfixed
        #
        if unfixed:
            unfixed_vars = set()
            for v in unfixed:
                if v.is_indexed():
                    for vardata in v.values():
                        unfixed_vars.add(id(vardata))
                else:
                    unfixed_vars.add(id(v))
            for id_, vdata in modelvars.items():
                if id_ not in unfixed_vars:
                    fixed_modelvars[id_] = vdata
        #
        # ... or fix everything that is specified as fixed
        #
        elif fixed:
            fixed_vars = set()
            for v in fixed:
                if v.is_indexed():
                    for vardata in v.values():
                        fixed_vars.add(id(vardata))
                else:
                    fixed_vars.add(id(v))
            for id_ in fixed_vars:
                if id_ in modelvars:
                    fixed_modelvars[id_] = modelvars[id_]

    A, b_coef, obj_constant, c_rhs, c_sense, d_sense, v_domain =\
                    collect_dual_representation(block, fixed_modelvars)

    #
    # Construct the block
    #
    if isinstance(block, Model):
        dual = ConcreteModel()
    else:
        dual = Block()
    dual.construct()
    _vars = {}

    # Return variable object from name and index (if applicable)
    def getvar(name, ndx=None):
        v = _vars.get((name, ndx), None)
        if v is None:
            v = Var()
            if ndx is None:
                v_name = name
            elif isinstance(ndx, tuple):
                v_name = "%s[%s]" % (name, ','.join(map(str, ndx)))
            else:
                v_name = "%s[%s]" % (name, str(ndx))
            setattr(dual, v_name, v)
            _vars[name, ndx] = v
        return v

    #
    # Construct the objective
    # The dualization of a maximization problem is handled by simply negating the
    # objective and left-hand side coefficients while keeping the dual sense.
    #
    if d_sense == minimize:
        dual.o = Objective(expr=obj_constant +
                           sum(-b_coef[name, ndx] * getvar(name, ndx)
                               for name, ndx in b_coef),
                           sense=d_sense)
        rhs_multiplier = -1
    else:
        dual.o = Objective(expr=obj_constant +
                           sum(b_coef[name, ndx] * getvar(name, ndx)
                               for name, ndx in b_coef),
                           sense=d_sense)
        rhs_multiplier = 1
    #
    # Construct the constraints from dual A matrix
    #
    for cname in A:
        for ndx, terms in A[cname].items():

            # Build left-hand side of constraint
            expr = 0
            for term in terms:
                expr += term.coef * getvar(term.var, term.ndx)

            #
            # Assign right-hand side coefficient
            # Note that rhs_multiplier is 1 if the dual is a maximization problem and -1 otherwise
            #
            rhsval = rhs_multiplier * c_rhs.get((cname, ndx), 0.0)

            # Using the correct inequality or equality
            if c_sense[cname, ndx] == 'e':
                e = expr - rhsval == 0
            elif c_sense[cname, ndx] == 'l':
                e = expr - rhsval <= 0
            else:
                e = expr - rhsval >= 0
            c = Constraint(expr=e)

            # Build constraint name
            if ndx is None:
                c_name = cname
            elif isinstance(ndx, tuple):
                c_name = "%s[%s]" % (cname, ','.join(map(str, ndx)))
            else:
                c_name = "%s[%s]" % (cname, str(ndx))

            # Add new constraint along with its name to the dual
            setattr(dual, c_name, c)

        # Set variable domains
        for (name, ndx), domain in v_domain.items():
            v = getvar(name, ndx)
            #flag = type(ndx) is tuple and (ndx[-1] == 'lb' or ndx[-1] == 'ub')
            if domain == 1:
                v.domain = NonNegativeReals
            elif domain == -1:
                v.domain = NonPositiveReals
            else:
                # This is possible when the variable's corresponding constraint is an equality
                v.domain = Reals

    return dual
Exemple #21
0
 def test_identify_vars_vars(self):
     m = ConcreteModel()
     m.I = RangeSet(3)
     m.a = Var(initialize=1)
     m.b = Var(m.I, initialize=1)
     m.p = Param(initialize=1, mutable=True)
     m.x = ExternalFunction(library='foo.so', function='bar')
     #
     # Identify variables in various algebraic expressions
     #
     self.assertEqual( list(identify_variables(m.a)), [m.a] )
     self.assertEqual( list(identify_variables(m.b[1])), [m.b[1]] )
     self.assertEqual( list(identify_variables(m.a+m.b[1])),
                       [ m.a, m.b[1] ] )
     self.assertEqual( list(identify_variables(m.a**m.b[1])),
                       [ m.a, m.b[1] ] )
     self.assertEqual( list(identify_variables(m.a**m.b[1] + m.b[2])),
                       [ m.a, m.b[1], m.b[2] ] )
     self.assertEqual( list(identify_variables(
         m.a**m.b[1] + m.b[2]*m.b[3]*m.b[2])),
                       [ m.a, m.b[1], m.b[2], m.b[3] ] )
     self.assertEqual( list(identify_variables(
         m.a**m.b[1] + m.b[2]/m.b[3]*m.b[2])),
                       [ m.a, m.b[1], m.b[2], m.b[3] ] )
     #
     # Identify variables in the arguments to functions
     #
     self.assertEqual( list(identify_variables(
         m.x(m.a, 'string_param', 1, [])*m.b[1] )),
                       [ m.a, m.b[1] ] )
     self.assertEqual( list(identify_variables(
         m.x(m.p, 'string_param', 1, [])*m.b[1] )),
                       [ m.b[1] ] )
     self.assertEqual( list(identify_variables(
         tanh(m.a)*m.b[1] )), [ m.a, m.b[1] ] )
     self.assertEqual( list(identify_variables(
         abs(m.a)*m.b[1] )), [ m.a, m.b[1] ] )
     #
     # Check logic for allowing duplicates
     #
     self.assertEqual( list(identify_variables(m.a**m.a + m.a)),
                       [ m.a ] )
Exemple #22
0
def add_affine_cuts(nlp_result, solve_data, config):
    with time_code(solve_data.timing, "affine cut generation"):
        m = solve_data.linear_GDP
        if config.calc_disjunctive_bounds:
            with time_code(solve_data.timing, "disjunctive variable bounding"):
                TransformationFactory('contrib.compute_disj_var_bounds').\
                    apply_to( m, solver=config.mip_solver if
                              config.obbt_disjunctive_bounds else None )
        config.logger.info("Adding affine cuts.")
        GDPopt = m.GDPopt_utils
        counter = 0
        for var, val in zip(GDPopt.variable_list, nlp_result.var_values):
            if val is not None and not var.fixed:
                var.set_value(val, skip_validation=True)

        for constr in constraints_in_True_disjuncts(m, config):
            # Note: this includes constraints that are deactivated in the
            # current model (linear_GDP)

            disjunctive_var_bounds = disjunctive_bounds(constr.parent_block())

            if constr.body.polynomial_degree() in (1, 0):
                continue

            vars_in_constr = list(identify_variables(constr.body))
            if any(var.value is None for var in vars_in_constr):
                continue  # a variable has no values

            # mcpp stuff
            try:
                mc_eqn = mc(constr.body, disjunctive_var_bounds)
            except MCPP_Error as e:
                config.logger.debug("Skipping constraint %s due to MCPP "
                                    "error %s" % (constr.name, str(e)))
                continue  # skip to the next constraint
            ccSlope = mc_eqn.subcc()
            cvSlope = mc_eqn.subcv()
            ccStart = mc_eqn.concave()
            cvStart = mc_eqn.convex()
            ub_int = min(constr.upper, mc_eqn.upper()) if constr.has_ub() \
                     else mc_eqn.upper()
            lb_int = max(constr.lower, mc_eqn.lower()) if constr.has_lb() \
                     else mc_eqn.lower()

            parent_block = constr.parent_block()
            # Create a block on which to put outer approximation cuts.
            aff_utils = parent_block.component('GDPopt_aff')
            if aff_utils is None:
                aff_utils = parent_block.GDPopt_aff = Block(
                    doc="Block holding affine constraints")
                aff_utils.GDPopt_aff_cons = ConstraintList()
            aff_cuts = aff_utils.GDPopt_aff_cons
            concave_cut = sum(ccSlope[var] * (var - var.value)
                              for var in vars_in_constr
                              if not var.fixed) + ccStart >= lb_int
            convex_cut = sum(cvSlope[var] * (var - var.value)
                             for var in vars_in_constr
                             if not var.fixed) + cvStart <= ub_int
            aff_cuts.add(expr=concave_cut)
            aff_cuts.add(expr=convex_cut)
            counter += 2

        config.logger.info("Added %s affine cuts" % counter)
Exemple #23
0
def log_infeasible_constraints(m,
                               tol=1E-6,
                               logger=logger,
                               log_expression=False,
                               log_variables=False):
    """Print the infeasible constraints in the model.

    Uses the current model state. Uses pyomo.util.infeasible logger unless one
    is provided.

    Args:
        m (Block): Pyomo block or model to check
        tol (float): feasibility tolerance
        log_expression (bool): If true, prints the constraint expression
        log_variables (bool): If true, prints the constraint variable names and values

    """
    # Iterate through all active constraints on the model
    for constr in m.component_data_objects(ctype=Constraint,
                                           active=True,
                                           descend_into=True):
        constr_body_value = value(constr.body, exception=False)
        constr_lb_value = value(constr.lower, exception=False)
        constr_ub_value = value(constr.upper, exception=False)

        constr_undefined = False
        equality_violated = False
        lb_violated = False
        ub_violated = False

        if constr_body_value is None:
            # Undefined constraint body value due to missing variable value
            constr_undefined = True
            pass
        else:
            # Check for infeasibilities
            if constr.equality:
                if fabs(constr_lb_value - constr_body_value) >= tol:
                    equality_violated = True
            else:
                if constr.has_lb() and fabs(constr_lb_value -
                                            constr_body_value) >= tol:
                    lb_violated = True
                if constr.has_ub() and fabs(constr_body_value -
                                            constr_ub_value) >= tol:
                    ub_violated = True

        if not any(
            (constr_undefined, equality_violated, lb_violated, ub_violated)):
            # constraint is fine. skip to next constraint
            continue

        output_dict = dict(name=constr.name)

        log_template = "CONSTR {name}: {lb_value}{lb_operator}{body_value}{ub_operator}{ub_value}"
        if log_expression:
            log_template += "\n  {lb_expr}{lb_operator}{body_expr}{ub_operator}{ub_expr}"
        if log_variables:
            vars_template = "  VAR {name}: {value}"
            log_template += "\n{var_printout}"
            constraint_vars = identify_variables(constr.body,
                                                 include_fixed=True)
            output_dict['var_printout'] = '\n'.join(
                vars_template.format(name=v.name, value=v.value)
                for v in constraint_vars)

        output_dict[
            'body_value'] = "missing variable value" if constr_undefined else constr_body_value
        output_dict['body_expr'] = constr.body
        if constr.equality:
            output_dict['lb_value'] = output_dict['lb_expr'] = output_dict[
                'lb_operator'] = ""
            output_dict['ub_value'] = constr_ub_value
            output_dict['ub_expr'] = constr.upper
            if equality_violated:
                output_dict['ub_operator'] = " =/= "
            elif constr_undefined:
                output_dict['ub_operator'] = " =?= "
        else:
            if constr.has_lb():
                output_dict['lb_value'] = constr_lb_value
                output_dict['lb_expr'] = constr.lower
                if lb_violated:
                    output_dict['lb_operator'] = " </= "
                elif constr_undefined:
                    output_dict['lb_operator'] = " <?= "
                else:
                    output_dict['lb_operator'] = " <= "
            else:
                output_dict['lb_value'] = output_dict['lb_expr'] = output_dict[
                    'lb_operator'] = ""

            if constr.has_ub():
                output_dict['ub_value'] = constr_ub_value
                output_dict['ub_expr'] = constr.upper
                if ub_violated:
                    output_dict['ub_operator'] = " </= "
                elif constr_undefined:
                    output_dict['ub_operator'] = " <?= "
                else:
                    output_dict['ub_operator'] = " <= "
            else:
                output_dict['ub_value'] = output_dict['ub_expr'] = output_dict[
                    'ub_operator'] = ""

        logger.info(log_template.format(**output_dict))
Exemple #24
0
def add_outer_approximation_cuts(nlp_result, solve_data, config):
    """Add outer approximation cuts to the linear GDP model."""
    with time_code(solve_data.timing, 'OA cut generation'):
        m = solve_data.linear_GDP
        GDPopt = m.GDPopt_utils
        sign_adjust = -1 if solve_data.objective_sense == minimize else 1

        # copy values over
        for var, val in zip(GDPopt.variable_list, nlp_result.var_values):
            if val is not None and not var.fixed:
                var.value = val

        # TODO some kind of special handling if the dual is phenomenally small?
        config.logger.debug('Adding OA cuts.')

        counter = 0
        if not hasattr(GDPopt, 'jacobians'):
            GDPopt.jacobians = ComponentMap()
        for constr, dual_value in zip(GDPopt.constraint_list,
                                      nlp_result.dual_values):
            if dual_value is None or constr.body.polynomial_degree() in (1, 0):
                continue

            # Determine if the user pre-specified that OA cuts should not be
            # generated for the given constraint.
            parent_block = constr.parent_block()
            ignore_set = getattr(parent_block, 'GDPopt_ignore_OA', None)
            config.logger.debug('Ignore_set %s' % ignore_set)
            if (ignore_set and (constr in ignore_set
                                or constr.parent_component() in ignore_set)):
                config.logger.debug(
                    'OA cut addition for %s skipped because it is in '
                    'the ignore set.' % constr.name)
                continue

            config.logger.debug("Adding OA cut for %s with dual value %s" %
                                (constr.name, dual_value))

            # Cache jacobian
            jacobian = GDPopt.jacobians.get(constr, None)
            if jacobian is None:
                constr_vars = list(
                    identify_variables(constr.body, include_fixed=False))
                if len(constr_vars) >= MAX_SYMBOLIC_DERIV_SIZE:
                    mode = differentiate.Modes.reverse_numeric
                else:
                    mode = differentiate.Modes.sympy

                try:
                    jac_list = differentiate(constr.body,
                                             wrt_list=constr_vars,
                                             mode=mode)
                    jac_map = ComponentMap(zip(constr_vars, jac_list))
                except:
                    if mode is differentiate.Modes.reverse_numeric:
                        raise
                    mode = differentiate.Modes.reverse_numeric
                    jac_map = ComponentMap()
                jacobian = JacInfo(mode=mode, vars=constr_vars, jac=jac_map)
                GDPopt.jacobians[constr] = jacobian
            # Recompute numeric derivatives
            if not jacobian.jac:
                jac_list = differentiate(constr.body,
                                         wrt_list=jacobian.vars,
                                         mode=jacobian.mode)
                jacobian.jac.update(zip(jacobian.vars, jac_list))

            # Create a block on which to put outer approximation cuts.
            oa_utils = parent_block.component('GDPopt_OA')
            if oa_utils is None:
                oa_utils = parent_block.GDPopt_OA = Block(
                    doc="Block holding outer approximation cuts "
                    "and associated data.")
                oa_utils.GDPopt_OA_cuts = ConstraintList()
                oa_utils.GDPopt_OA_slacks = VarList(bounds=(0,
                                                            config.max_slack),
                                                    domain=NonNegativeReals,
                                                    initialize=0)

            oa_cuts = oa_utils.GDPopt_OA_cuts
            slack_var = oa_utils.GDPopt_OA_slacks.add()
            rhs = value(constr.lower) if constr.has_lb() else value(
                constr.upper)
            try:
                new_oa_cut = (copysign(1, sign_adjust * dual_value) *
                              (value(constr.body) - rhs + sum(
                                  value(jac) * (var - value(var))
                                  for var, jac in iteritems(jacobian.jac))) -
                              slack_var <= 0)
                if new_oa_cut.polynomial_degree() not in (1, 0):
                    for var, jac in iteritems(jacobian.jac):
                        print(var.name, value(jac))
                oa_cuts.add(expr=new_oa_cut)
                counter += 1
            except ZeroDivisionError:
                config.logger.warning(
                    "Zero division occured attempting to generate OA cut for constraint %s.\n"
                    "Skipping OA cut generation for this constraint." %
                    (constr.name, ))
                # Simply continue on to the next constraint.
            # Clear out the numeric Jacobian values
            if jacobian.mode is differentiate.Modes.reverse_numeric:
                jacobian.jac.clear()

        config.logger.info('Added %s OA cuts' % counter)
Exemple #25
0
 def test_identify_vars_linear_expression(self):
     m = ConcreteModel()
     m.x = Var()
     expr = quicksum([m.x, m.x], linear=True)
     self.assertEqual(list(identify_variables(
         expr, include_fixed=False)), [m.x])
Exemple #26
0
def add_outer_approximation_cuts(nlp_result, solve_data, config):
    """Add outer approximation cuts to the linear GDP model."""
    with time_code(solve_data.timing, 'OA cut generation'):
        m = solve_data.linear_GDP
        GDPopt = m.GDPopt_utils
        sign_adjust = -1 if solve_data.objective_sense == minimize else 1

        # copy values over
        for var, val in zip(GDPopt.variable_list, nlp_result.var_values):
            if val is not None and not var.fixed:
                var.value = val

        # TODO some kind of special handling if the dual is phenomenally small?
        config.logger.debug('Adding OA cuts.')

        counter = 0
        if not hasattr(GDPopt, 'jacobians'):
            GDPopt.jacobians = ComponentMap()
        for constr, dual_value in zip(GDPopt.constraint_list,
                                      nlp_result.dual_values):
            if dual_value is None or constr.body.polynomial_degree() in (1, 0):
                continue

            # Determine if the user pre-specified that OA cuts should not be
            # generated for the given constraint.
            parent_block = constr.parent_block()
            ignore_set = getattr(parent_block, 'GDPopt_ignore_OA', None)
            config.logger.debug('Ignore_set %s' % ignore_set)
            if (ignore_set and (constr in ignore_set or
                                constr.parent_component() in ignore_set)):
                config.logger.debug(
                    'OA cut addition for %s skipped because it is in '
                    'the ignore set.' % constr.name)
                continue

            config.logger.debug(
                "Adding OA cut for %s with dual value %s"
                % (constr.name, dual_value))

            # Cache jacobians
            jacobians = GDPopt.jacobians.get(constr, None)
            if jacobians is None:
                constr_vars = list(identify_variables(constr.body))
                jac_list = differentiate(constr.body, wrt_list=constr_vars)
                jacobians = ComponentMap(zip(constr_vars, jac_list))
                GDPopt.jacobians[constr] = jacobians

            # Create a block on which to put outer approximation cuts.
            oa_utils = parent_block.component('GDPopt_OA')
            if oa_utils is None:
                oa_utils = parent_block.GDPopt_OA = Block(
                    doc="Block holding outer approximation cuts "
                    "and associated data.")
                oa_utils.GDPopt_OA_cuts = ConstraintList()
                oa_utils.GDPopt_OA_slacks = VarList(
                    bounds=(0, config.max_slack),
                    domain=NonNegativeReals, initialize=0)

            oa_cuts = oa_utils.GDPopt_OA_cuts
            slack_var = oa_utils.GDPopt_OA_slacks.add()
            rhs = value(constr.lower) if constr.has_lb() else value(constr.upper)
            oa_cuts.add(
                expr=copysign(1, sign_adjust * dual_value) * (
                    value(constr.body) - rhs + sum(
                        value(jacobians[var]) * (var - value(var))
                        for var in jacobians)) - slack_var <= 0)
            counter += 1

        config.logger.info('Added %s OA cuts' % counter)
Exemple #27
0
    def test_cubic(self):
        m = pe.ConcreteModel()
        m.x = pe.Var(bounds=(-1, 1))
        m.y = pe.Var()
        m.z = pe.Var()
        m.w = pe.Var()
        m.c = pe.Constraint(expr=m.x**3 + m.y + m.z == 0)
        m.c2 = pe.Constraint(expr=m.w - 3 * m.x**3 == 0)

        rel = coramin.relaxations.relax(m)

        # this problem should turn into
        #
        # aux2 + y + z = 0        => aux_con[1]
        # w - 3*aux2 = 0          => aux_con[2]
        # aux1 = x**2             => rel0
        # aux2 = x*aux1           => rel1

        self.assertTrue(hasattr(rel, 'aux_cons'))
        self.assertTrue(hasattr(rel, 'aux_vars'))
        self.assertEqual(len(rel.aux_cons), 2)
        self.assertEqual(len(rel.aux_vars), 2)

        self.assertAlmostEqual(rel.aux_vars[1].lb, 0)
        self.assertAlmostEqual(rel.aux_vars[1].ub, 1)

        self.assertAlmostEqual(rel.aux_vars[2].lb, -1)
        self.assertAlmostEqual(rel.aux_vars[2].ub, 1)

        self.assertEqual(rel.aux_cons[1].lower, 0)
        self.assertEqual(rel.aux_cons[1].upper, 0)
        ders = reverse_sd(rel.aux_cons[1].body)
        self.assertEqual(ders[rel.z], 1)
        self.assertEqual(ders[rel.aux_vars[2]], 1)
        self.assertEqual(ders[rel.y], 1)
        self.assertEqual(len(list(identify_variables(rel.aux_cons[1].body))),
                         3)

        self.assertEqual(rel.aux_cons[2].lower, 0)
        self.assertEqual(rel.aux_cons[2].upper, 0)
        ders = reverse_sd(rel.aux_cons[2].body)
        self.assertEqual(ders[rel.w], 1)
        self.assertEqual(ders[rel.aux_vars[2]], -3)
        self.assertEqual(len(list(identify_variables(rel.aux_cons[2].body))),
                         2)

        self.assertTrue(hasattr(rel, 'relaxations'))
        self.assertTrue(hasattr(rel.relaxations, 'rel0'))
        self.assertTrue(
            isinstance(rel.relaxations.rel0,
                       coramin.relaxations.PWXSquaredRelaxation))
        self.assertIn(rel.x, ComponentSet(rel.relaxations.rel0.get_rhs_vars()))
        self.assertEqual(id(rel.aux_vars[1]),
                         id(rel.relaxations.rel0.get_aux_var()))

        self.assertTrue(hasattr(rel.relaxations, 'rel1'))
        self.assertTrue(
            isinstance(rel.relaxations.rel1,
                       coramin.relaxations.PWMcCormickRelaxation))
        self.assertIn(rel.x, ComponentSet(rel.relaxations.rel1.get_rhs_vars()))
        self.assertIn(rel.aux_vars[1],
                      ComponentSet(rel.relaxations.rel1.get_rhs_vars()))
        self.assertEqual(id(rel.aux_vars[2]),
                         id(rel.relaxations.rel1.get_aux_var()))
Exemple #28
0
    def test_pow_neg(self):
        m = pe.ConcreteModel()
        m.x = pe.Var(bounds=(-1, 1))
        m.y = pe.Var()
        m.z = pe.Var()
        m.w = pe.Var()
        m.p = pe.Param(initialize=-2)
        m.c = pe.Constraint(expr=m.x**m.p + m.y + m.z == 0)
        m.c2 = pe.Constraint(expr=m.w - 3 * m.x**m.p == 0)

        rel = coramin.relaxations.relax(m)

        # This model should be relaxed to
        #
        # aux2 + y + z = 0
        # w - 3 * aux2 = 0
        # aux1 = x**2
        # aux1*aux2 = aux3
        # aux3 = 1
        #

        self.assertTrue(hasattr(rel, 'aux_cons'))
        self.assertTrue(hasattr(rel, 'aux_vars'))
        self.assertEqual(len(rel.aux_cons), 2)
        self.assertEqual(len(rel.aux_vars), 3)

        self.assertAlmostEqual(rel.aux_vars[1].lb, 0)
        self.assertAlmostEqual(rel.aux_vars[1].ub, 1)

        self.assertTrue(rel.aux_vars[3].is_fixed())
        self.assertEqual(rel.aux_vars[3].value, 1)

        self.assertEqual(rel.aux_cons[1].lower, 0)
        self.assertEqual(rel.aux_cons[1].upper, 0)
        ders = reverse_sd(rel.aux_cons[1].body)
        self.assertEqual(ders[rel.z], 1)
        self.assertEqual(ders[rel.aux_vars[2]], 1)
        self.assertEqual(ders[rel.y], 1)
        self.assertEqual(len(list(identify_variables(rel.aux_cons[1].body))),
                         3)

        self.assertEqual(rel.aux_cons[2].lower, 0)
        self.assertEqual(rel.aux_cons[2].upper, 0)
        ders = reverse_sd(rel.aux_cons[2].body)
        self.assertEqual(ders[rel.w], 1)
        self.assertEqual(ders[rel.aux_vars[2]], -3)
        self.assertEqual(len(list(identify_variables(rel.aux_cons[2].body))),
                         2)

        self.assertTrue(hasattr(rel, 'relaxations'))
        self.assertTrue(hasattr(rel.relaxations, 'rel0'))
        self.assertTrue(
            isinstance(rel.relaxations.rel0,
                       coramin.relaxations.PWXSquaredRelaxation))
        self.assertIn(rel.x, ComponentSet(rel.relaxations.rel0.get_rhs_vars()))
        self.assertEqual(id(rel.aux_vars[1]),
                         id(rel.relaxations.rel0.get_aux_var()))
        self.assertTrue(rel.relaxations.rel0.is_rhs_convex())
        self.assertFalse(rel.relaxations.rel0.is_rhs_concave())

        self.assertTrue(hasattr(rel.relaxations, 'rel1'))
        self.assertTrue(
            isinstance(rel.relaxations.rel1,
                       coramin.relaxations.PWMcCormickRelaxation))
        self.assertIn(rel.aux_vars[1],
                      ComponentSet(rel.relaxations.rel1.get_rhs_vars()))
        self.assertIn(rel.aux_vars[2],
                      ComponentSet(rel.relaxations.rel1.get_rhs_vars()))
        self.assertEqual(id(rel.aux_vars[3]),
                         id(rel.relaxations.rel1.get_aux_var()))

        self.assertFalse(hasattr(rel.relaxations, 'rel2'))
Exemple #29
0
    def replaceExternalFunctionsWithVariables(self):
        """
        This method sets up essential data objects on the new trf_data block
        on the model as well as triggers the replacement of external functions
        in expressions trees.

        Data objects created:
            self.data.all_variables : ComponentSet
                A set of all variables on the model, including "holder"
                variables from the EF replacement
            self.data.truth_models : ComponentMap
                A component map for replaced nodes that keeps track of
                the truth model for that replacement.
            self.data.basis_expressions : ComponentMap
                A component map for the Pyomo expressions for basis functions
                as they apply to each variable
            self.data.ef_inputs : Dict
                A dictionary that tracks the input variables for each EF
            self.data.ef_outputs : VarList
                A list of the "holder" variables which replaced the original
                External Function expressions
        """
        self.data.all_variables = ComponentSet()
        self.data.truth_models = ComponentMap()
        self.data.basis_expressions = ComponentMap()
        self.data.ef_inputs = {}
        self.data.ef_outputs = VarList()

        number_of_equality_constraints = 0
        for con in self.model.component_data_objects(Constraint, active=True):
            if con.lb == con.ub and con.lb is not None:
                number_of_equality_constraints += 1
            self._remove_ef_from_expr(con)

        self.degrees_of_freedom = (len(list(self.data.all_variables)) -
                                   number_of_equality_constraints)
        if self.degrees_of_freedom != len(self.decision_variables):
            raise ValueError(
                "replaceExternalFunctionsWithVariables: "
                "The degrees of freedom %d do not match the number of decision "
                "variables supplied %d." %
                (self.degrees_of_freedom, len(self.decision_variables)))

        for var in self.decision_variables:
            if var not in self.data.all_variables:
                raise ValueError(
                    "replaceExternalFunctionsWithVariables: "
                    f"The supplied decision variable {var.name} cannot "
                    "be found in the model variables.")

        self.data.objs = list(
            self.model.component_data_objects(Objective, active=True))
        # HACK: This is a hack that we will want to remove once the NL writer
        # has been corrected to not send unused EFs to the solver
        for ef in self.model.component_objects(ExternalFunction):
            ef.parent_block().del_component(ef)

        if len(self.data.objs) != 1:
            raise ValueError(
                "replaceExternalFunctionsWithVariables: "
                "TrustRegion only supports models with a single active Objective."
            )
        if self.data.objs[0].sense == maximize:
            self.data.objs[0].expr = -1 * self.data.objs[0].expr
            self.data.objs[0].sense = minimize
        self._remove_ef_from_expr(self.data.objs[0])

        for i in self.data.ef_outputs:
            self.data.ef_inputs[i] = \
                list(identify_variables(
                    self.data.truth_models[self.data.ef_outputs[i]],
                    include_fixed=False)
                )
        self.data.all_variables.update(self.data.ef_outputs.values())
        self.data.all_variables = list(self.data.all_variables)