Ejemplo n.º 1
0
def test_set_dae_suffix():
    m, y1, y2, y3, y4, y5, y6 = dae_with_non_time_indexed_constraint()
    regular_vars, time_vars = pyodae.flatten.flatten_dae_components(
        m, m.t, pyo.Var)
    regular_cons, time_cons = pyodae.flatten.flatten_dae_components(
        m, m.t, pyo.Constraint)
    t = 180
    m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT)
    m.scaling_factor[m.ydot[180, 2]] = 10
    constraints = [con[t] for con in time_cons if t in con]
    variables = [var[t] for var in time_vars]
    t_block = create_subsystem_block(constraints, variables)
    deriv_diff_map = petsc._get_derivative_differential_data_map(m, m.t)
    petsc._set_dae_suffixes_from_variables(t_block, variables, deriv_diff_map)
    petsc._sub_problem_scaling_suffix(m, t_block)

    assert t_block.dae_suffix[m.ydot[180, 1]] == 2
    assert t_block.dae_suffix[m.ydot[180, 2]] == 2
    assert t_block.dae_suffix[m.ydot[180, 3]] == 2
    assert t_block.dae_suffix[m.ydot[180, 4]] == 2
    assert t_block.dae_suffix[m.ydot[180, 5]] == 2
    assert t_block.scaling_factor[m.ydot[180, 2]] == 10
    assert t_block.dae_suffix[m.y[180, 1]] == 1
    assert t_block.dae_suffix[m.y[180, 2]] == 1
    assert t_block.dae_suffix[m.y[180, 3]] == 1
    assert t_block.dae_suffix[m.y[180, 4]] == 1
    assert t_block.dae_suffix[m.y[180, 5]] == 1

    # Make sure deactivating a differential equation makes the variable that
    # would have been differential go algebraic
    m, y1, y2, y3, y4, y5, y6 = dae_with_non_time_indexed_constraint()
    # discretization equations would be deactivated in normal PETSc solve
    for con in petsc.find_discretization_equations(m, m.t):
        con.deactivate()
    # deactivate a differential equation making y4 be algebraic
    m.eq_ydot4[180].deactivate()
    regular_vars, time_vars = pyodae.flatten.flatten_dae_components(
        m, m.t, pyo.Var)
    regular_cons, time_cons = pyodae.flatten.flatten_dae_components(
        m, m.t, pyo.Constraint)
    t = 180
    constraints = [con[t] for con in time_cons if t in con]
    variables = [var[t] for var in time_vars]
    t_block = create_subsystem_block(constraints, variables)
    deriv_diff_map = petsc._get_derivative_differential_data_map(m, m.t)
    petsc._set_dae_suffixes_from_variables(t_block, variables, deriv_diff_map)
    petsc._sub_problem_scaling_suffix(m, t_block)

    assert m.y[t, 4] not in t_block.dae_suffix
    assert m.ydot[t, 4] not in t_block.dae_suffix
Ejemplo n.º 2
0
    def test_subsystem_inputs_only(self):
        m = _make_simple_model()

        cons = [m.con2, m.con3]
        block = create_subsystem_block(cons)

        self.assertEqual(len(block.vars), 0)
        self.assertEqual(len(block.input_vars), 4)
        self.assertEqual(len(block.cons), 2)

        self.assertEqual(len([v for v in block.component_data_objects(pyo.Var)
            if not v.fixed]), 4)

        block.input_vars.fix()
        self.assertEqual(len([v for v in block.component_data_objects(pyo.Var)
            if not v.fixed]), 0)

        var_set = ComponentSet([m.v1, m.v2, m.v3, m.v4])
        self.assertIs(block.cons[0], m.con2)
        self.assertIs(block.cons[1], m.con3)
        self.assertIn(block.input_vars[0], var_set)
        self.assertIn(block.input_vars[1], var_set)
        self.assertIn(block.input_vars[2], var_set)
        self.assertIn(block.input_vars[3], var_set)

        # Make sure block is not part of the original model's tree. We
        # don't want to alter the user's model at all.
        self.assertIsNot(block.model(), m)

        # Components on the block are references to components on the
        # original model
        for comp in block.component_objects((pyo.Var, pyo.Constraint)):
            self.assertTrue(comp.is_reference())
            for data in comp.values():
                self.assertIs(data.model(), m)
Ejemplo n.º 3
0
    def test_solve_subsystem(self):
        # This is a test of this function's intended use. We extract a
        # subsystem then solve it without altering the rest of the model.
        m = _make_simple_model()
        ipopt = pyo.SolverFactory("ipopt")

        m.v5 = pyo.Var(initialize=1.0)
        m.c4 = pyo.Constraint(expr=m.v5 == 5.0)

        cons = [m.con2, m.con3]
        vars = [m.v1, m.v2]
        block = create_subsystem_block(cons, vars)

        m.v3.fix(1.0)
        m.v4.fix(2.0)

        # Initialize to avoid converging infeasible due to bad pivots
        m.v1.set_value(1.0)
        m.v2.set_value(1.0)
        ipopt.solve(block)

        # Have solved model to expected values
        self.assertAlmostEqual(m.v1.value, pyo.sqrt(7.0), delta=1e-8)
        self.assertAlmostEqual(m.v2.value, pyo.sqrt(4.0-pyo.sqrt(7.0)),
                delta=1e-8)

        # Rest of model has not changed
        self.assertEqual(m.v5.value, 1.0)
Ejemplo n.º 4
0
    def __init__(
        self,
        input_vars,
        external_vars,
        residual_cons,
        external_cons,
        solver=None,
    ):
        if solver is None:
            solver = SolverFactory("ipopt")
        self._solver = solver

        # We only need this block to construct the NLP, which wouldn't
        # be necessary if we could compute Hessians of Pyomo constraints.
        self._block = create_subsystem_block(
            residual_cons + external_cons,
            input_vars + external_vars,
        )
        self._block._obj = Objective(expr=0.0)
        self._nlp = PyomoNLP(self._block)

        self._scc_list = list(
            generate_strongly_connected_components(external_cons,
                                                   variables=external_vars))

        assert len(external_vars) == len(external_cons)

        self.input_vars = input_vars
        self.external_vars = external_vars
        self.residual_cons = residual_cons
        self.external_cons = external_cons

        self.residual_con_multipliers = [None for _ in residual_cons]
        self.residual_scaling_factors = None
Ejemplo n.º 5
0
def get_hessian_of_constraint(constraint, wrt1=None, wrt2=None, nlp=None):
    constraints = [constraint]
    if wrt1 is None and wrt2 is None:
        variables = list(
            identify_variables(constraint.expr, include_fixed=False))
        wrt1 = variables
        wrt2 = variables
    elif wrt1 is not None and wrt2 is not None:
        variables = wrt1 + wrt2
    elif wrt1 is not None:  # but wrt2 is None
        wrt2 = wrt1
        variables = wrt1
    else:
        # wrt2 is not None and wrt1 is None
        wrt1 = wrt2
        variables = wrt1

    if nlp is None:
        block = create_subsystem_block(constraints, variables=variables)
        # Could fix input_vars so I don't evaluate the Hessian with respect
        # to variables I don't care about...

        # HUGE HACK: Variables not included in a constraint are not written
        # to the nl file, so we cannot take the derivative with respect to
        # them, even though we know this derivative is zero. To work around,
        # we make sure all variables appear on the block in the form of a
        # dummy constraint. Then we can take derivatives of any constraint
        # with respect to them. Conveniently, the extract_submatrix_
        # call deals with extracting the variables and constraint we care
        # about, in the proper order.
        block._dummy_var = Var()
        block._dummy_con = Constraint(expr=sum(variables) == block._dummy_var)
        block._obj = Objective(expr=0.0)
        nlp = PyomoNLP(block)

    saved_duals = nlp.get_duals()
    saved_obj_factor = nlp.get_obj_factor()
    temp_duals = np.zeros(len(saved_duals))

    # NOTE: This makes some assumption about how the Lagrangian is constructed.
    # TODO: Define the convention we assume and convert if necessary.
    idx = nlp.get_constraint_indices(constraints)[0]
    temp_duals[idx] = 1.0
    nlp.set_duals(temp_duals)
    nlp.set_obj_factor(0.0)

    # NOTE: The returned matrix preserves explicit zeros. I.e. it contains
    # coordinates for every entry that could possibly be nonzero.
    submatrix = nlp.extract_submatrix_hessian_lag(wrt1, wrt2)

    nlp.set_obj_factor(saved_obj_factor)
    nlp.set_duals(saved_duals)
    return submatrix
Ejemplo n.º 6
0
def get_numeric_incidence_matrix(variables, constraints):
    """
    This function gets the numeric incidence matrix (Jacobian) of Pyomo
    constraints with respect to variables.
    """
    # NOTE: There are several ways to get a numeric incidence matrix
    # from a Pyomo model. Here we get the numeric incidence matrix by
    # creating a temporary block and using the PyNumero ASL interface.
    comps = list(variables) + list(constraints)
    _check_unindexed(comps)
    block = create_subsystem_block(constraints, variables)
    block._obj = Objective(expr=0)
    nlp = PyomoNLP(block)
    return nlp.extract_submatrix_jacobian(variables, constraints)
Ejemplo n.º 7
0
    def test_generate_subsystems_with_exception(self):
        m = _make_simple_model()
        subsystems = [ 
                ([m.con1], [m.v1, m.v4]),
                ([m.con2, m.con3], [m.v2, m.v3]),
                ]
        other_vars = [ 
                [m.v2, m.v3],
                [m.v1, m.v4],
                ]
        block = create_subsystem_block(*subsystems[0])
        with self.assertRaises(RuntimeError):
            inputs = list(block.input_vars[:])
            with TemporarySubsystemManager(to_fix=inputs):
                self.assertTrue(all(var.fixed for var in inputs))
                self.assertFalse(any(var.fixed for var in block.vars[:]))
                raise RuntimeError()

        # Test that we have properly unfixed variables
        self.assertFalse(any(var.fixed for var in
            m.component_data_objects(pyo.Var)))
Ejemplo n.º 8
0
    def test_with_external_function(self):
        m = self._make_model_with_external_functions()
        subsystem = ([m.con2, m.con3], [m.v2, m.v3])

        m.v1.set_value(0.5)
        block = create_subsystem_block(*subsystem)
        ipopt = pyo.SolverFactory("ipopt")
        with TemporarySubsystemManager(to_fix=list(block.input_vars.values())):
            ipopt.solve(block)

        # Correct values obtained by solving with Ipopt directly
        # in another script.
        self.assertEqual(m.v1.value, 0.5)
        self.assertFalse(m.v1.fixed)
        self.assertAlmostEqual(m.v2.value, 1.04816, delta=1e-5)
        self.assertAlmostEqual(m.v3.value, 1.34356, delta=1e-5)

        # Result obtained by solving the full system
        m_full = self._solve_ef_model_with_ipopt()
        self.assertAlmostEqual(m.v1.value, m_full.v1.value)
        self.assertAlmostEqual(m.v2.value, m_full.v2.value)
        self.assertAlmostEqual(m.v3.value, m_full.v3.value)
Ejemplo n.º 9
0
    def set_input_values(self, input_values):
        solver = self._solver
        external_cons = self.external_cons
        external_vars = self.external_vars
        input_vars = self.input_vars

        for var, val in zip(input_vars, input_values):
            var.set_value(val)

        _temp = create_subsystem_block(external_cons, variables=external_vars)
        possible_input_vars = ComponentSet(input_vars)
        #for var in _temp.input_vars.values():
        #    # TODO: Is this check necessary?
        #    assert var in possible_input_vars

        with TemporarySubsystemManager(to_fix=list(_temp.input_vars.values())):
            solver.solve(_temp)

        # Should we create the NLP from the original block or the temp block?
        # Need to create it from the original block because temp block won't
        # have residual constraints, whose derivatives are necessary.
        self._nlp = PyomoNLP(self._block)
Ejemplo n.º 10
0
def petsc_dae_by_time_element(
    m,
    time,
    timevar=None,
    initial_constraints=None,
    initial_variables=None,
    detect_initial=True,
    skip_initial=False,
    snes_options=None,
    ts_options=None,
    wsl=None,
    keepfiles=False,
    symbolic_solver_labels=True,
    vars_stub=None,
    trajectory_save_prefix=None,
):
    """Solve a DAE problem step by step using the PETSc DAE solver.  This
    integrates from one time point to the next.

    Args:
        m (Block): Pyomo model to solve
        time (ContinuousSet): Time set
        timevar (Var): Optional specification of a time variable, which can be
            used to write constraints that are an explicit function of time.
        initial_constraints (list): Constraints to solve in the initial
            condition solve step.  Since the time-indexed constraints are picked
            up automatically, this generally includes non-time-indexed
            constraints.
        initial_variables (list): This is a list of variables to fix after the
            initial condition solve step.  If these variables were originally
            unfixed, they will be unfixed at the end of the solve. This usually
            includes non-time-indexed variables that are calculated along with
            the initial conditions.
        detect_initial (bool): If True, add non-time-indexed variables and
            constraints to initial_variables and initial_constraints.
        skip_initial (bool): Don't do the initial condition calculation step,
            and assume that the initial condition values have already been
            calculated. This can be useful, for example, if you read initial
            conditions from a separately solved steady state problem, or
            otherwise know the initial conditions.
        snes_options (dict): PETSc nonlinear equation solver options
        ts_options (dict): PETSc time-stepping solver options
        wsl (bool): if True use WSL to run PETSc, if False don't use WSL to run
            PETSc, if None automatic. The WSL is only for Windows.
        keepfiles (bool): pass to keepfiles arg for solvers
        symbolic_solver_labels (bool): pass to symbolic_solver_labels argument
            for solvers. If you want to read trajectory data from the
            time-stepping solver, this should be True.
        vars_stub (str or None): Copy the `*.col` and `*.typ` files to the
            working directory using this stub if not None.  These are needed to
            interpret the trajectory data.
        trajectory_save_prefix (str or None): If a string is provided the
            trajectory data will be saved as gzipped json

    Returns:
        List of solver results objects from each solve. If there are initial
        condition constraints and they are not skipped, the first object will
        be from the initial condition solve.  Then there should be one for each
        time element for each TS solve.
    """
    solve_log = idaeslog.getSolveLogger("petsc-dae")
    regular_vars, time_vars = flatten_dae_components(m, time, pyo.Var)
    regular_cons, time_cons = flatten_dae_components(m, time, pyo.Constraint)
    tdisc = find_discretization_equations(m, time)

    solver_snes = pyo.SolverFactory("petsc_snes",
                                    options=snes_options,
                                    wsl=wsl)
    solver_dae = pyo.SolverFactory("petsc_ts",
                                   options=ts_options,
                                   wsl=wsl,
                                   vars_stub=vars_stub)

    if initial_variables is None:
        initial_variables = []
    if initial_constraints is None:
        initial_constraints = []

    if detect_initial:
        rvset = ComponentSet(regular_vars)
        rcset = ComponentSet(regular_cons)
        icset = ComponentSet(initial_constraints)
        ivset = ComponentSet(initial_variables)
        initial_variables = list(ivset | rvset)
        initial_constraints = list(icset | rcset)

    # First calculate the inital conditions and non-time-indexed constraints
    res_list = []
    t0 = time.first()
    if not skip_initial:
        with TemporarySubsystemManager(to_deactivate=tdisc):
            constraints = [con[t0] for con in time_cons if t0 in con
                           ] + initial_constraints
            variables = [var[t0] for var in time_vars] + initial_variables
            if len(constraints) > 0:
                # if the initial condition is specified and there are no
                # initial constraints, don't try to solve.
                t_block = create_subsystem_block(
                    constraints,
                    variables,
                )
                # set up the scaling factor suffix
                _sub_problem_scaling_suffix(m, t_block)
                with idaeslog.solver_log(solve_log, idaeslog.INFO) as slc:
                    res = solver_snes.solve(t_block, tee=slc.tee)
                res_list.append(res)

    tprev = t0
    count = 1
    fix_derivs = []
    with TemporarySubsystemManager(
            to_deactivate=tdisc,
            to_fix=initial_variables + fix_derivs,
    ):
        # Solver time steps
        deriv_diff_map = _get_derivative_differential_data_map(m, time)
        for t in time:
            if t == time.first():
                # t == time.first() was handled above
                continue
            constraints = [con[t] for con in time_cons if t in con]
            variables = [var[t] for var in time_vars]
            # Create a temporary block with references to original constraints
            # and variables so we can integrate this "subsystem" without
            # altering the rest of the model.
            t_block = create_subsystem_block(constraints, variables)
            differential_vars = _set_dae_suffixes_from_variables(
                t_block,
                variables,
                deriv_diff_map,
            )
            # We need to check if there are derivatives in the problem before
            # sending this to the solver.  We'll assume that if you are using
            # this and don't have any differential equations, you are making a
            # mistake.
            if len(differential_vars) < 1:
                raise RuntimeError(
                    "No differential equations found at t = %s, "
                    "you do not need a DAE solver." % t)
            if timevar is not None:
                t_block.dae_suffix[timevar[t]] = int(DaeVarTypes.TIME)
            # Set up the scaling factor suffix
            _sub_problem_scaling_suffix(m, t_block)
            # Take initial conditions for this step from the result of previous
            _copy_time(time_vars, tprev, t)
            with idaeslog.solver_log(solve_log, idaeslog.INFO) as slc:
                res = solver_dae.solve(
                    t_block,
                    tee=slc.tee,
                    keepfiles=keepfiles,
                    symbolic_solver_labels=symbolic_solver_labels,
                    export_nonlinear_variables=differential_vars,
                    options={
                        "--ts_init_time": tprev,
                        "--ts_max_time": t
                    },
                )
            if trajectory_save_prefix is not None:
                tj = PetscTrajectory(stub=vars_stub,
                                     delete_on_read=True,
                                     unscale=t_block)
                tj.to_json(f"{trajectory_save_prefix}_{count}.json.gz")
            tprev = t
            count += 1
            res_list.append(res)
    return res_list
Ejemplo n.º 11
0
    def __init__(
        self,
        input_vars,
        external_vars,
        residual_cons,
        external_cons,
        use_cyipopt=None,
        solver=None,
    ):
        """
        Arguments:
        ----------
        input_vars: list
            List of variables sent to this system by the outer solver
        external_vars: list
            List of variables that are solved for internally by this system
        residual_cons: list
            List of equality constraints whose residuals are exposed to
            the outer solver
        external_cons: list
            List of equality constraints used to solve for the external
            variables
        use_cyipopt: bool
            Whether to use CyIpopt to solve strongly connected components of
            the implicit function that have dimension greater than one.
        solver: Pyomo solver object
            Used to solve strongly connected components of the implicit function
            that have dimension greater than one. Only used if use_cyipopt
            is False.

        """
        if use_cyipopt is None:
            use_cyipopt = cyipopt_available
        if use_cyipopt and not cyipopt_available:
            raise RuntimeError(
                "Constructing an ExternalPyomoModel with CyIpopt unavailable. "
                "Please set the use_cyipopt argument to False.")
        if solver is not None and use_cyipopt:
            raise RuntimeError(
                "Constructing an ExternalPyomoModel with a solver specified "
                "and use_cyipopt set to True. Please set use_cyipopt to False "
                "to use the desired solver.")
        elif solver is None and not use_cyipopt:
            solver = SolverFactory("ipopt")
        # If use_cyipopt is True, this solver is None and will not be used.
        self._solver = solver
        self._use_cyipopt = use_cyipopt

        # We only need this block to construct the NLP, which wouldn't
        # be necessary if we could compute Hessians of Pyomo constraints.
        self._block = create_subsystem_block(
            residual_cons + external_cons,
            input_vars + external_vars,
        )
        self._block._obj = Objective(expr=0.0)
        self._nlp = PyomoNLP(self._block)

        self._scc_list = list(
            generate_strongly_connected_components(external_cons,
                                                   variables=external_vars))

        if use_cyipopt:
            # Using CyIpopt allows us to solve inner problems without
            # costly rewriting of the nl file. It requires quite a bit
            # of preprocessing, however, to construct the ProjectedNLP
            # for each block of the decomposition.

            # Get "vector-valued" SCCs, those of dimension > 0.
            # We will solve these with a direct IPOPT interface, which requires
            # some preprocessing.
            self._vector_scc_list = [(scc, inputs)
                                     for scc, inputs in self._scc_list
                                     if len(scc.vars) > 1]

            # Need a dummy objective to create an NLP
            for scc, inputs in self._vector_scc_list:
                scc._obj = Objective(expr=0.0)

                # I need scaling_factor so Pyomo NLPs I create from these blocks
                # don't break when ProjectedNLP calls get_primals_scaling
                scc.scaling_factor = Suffix(direction=Suffix.EXPORT)
                # HACK: scaling_factor just needs to be nonempty.
                scc.scaling_factor[scc._obj] = 1.0

            # These are the "original NLPs" that will be projected
            self._vector_scc_nlps = [
                PyomoNLP(scc) for scc, inputs in self._vector_scc_list
            ]
            self._vector_scc_var_names = [[
                var.name for var in scc.vars.values()
            ] for scc, inputs in self._vector_scc_list]
            self._vector_proj_nlps = [
                ProjectedNLP(nlp, names) for nlp, names in zip(
                    self._vector_scc_nlps, self._vector_scc_var_names)
            ]

            # We will solve the ProjectedNLPs rather than the original NLPs
            self._cyipopt_nlps = [
                CyIpoptNLP(nlp) for nlp in self._vector_proj_nlps
            ]
            self._cyipopt_solvers = [
                CyIpoptSolver(nlp) for nlp in self._cyipopt_nlps
            ]
            self._vector_scc_input_coords = [
                nlp.get_primal_indices(inputs) for nlp, (scc, inputs) in zip(
                    self._vector_scc_nlps, self._vector_scc_list)
            ]

        assert len(external_vars) == len(external_cons)

        self.input_vars = input_vars
        self.external_vars = external_vars
        self.residual_cons = residual_cons
        self.external_cons = external_cons

        self.residual_con_multipliers = [None for _ in residual_cons]
        self.residual_scaling_factors = None