Beispiel #1
0
    def test_generate_subsystems_with_fixed_var(self):
        m = _make_simple_model()
        m.v4.fix()
        subs = [
                ([m.con1], [m.v1]),
                ([m.con2, m.con3], [m.v2, m.v3]),
                ]
        other_vars = [
                [m.v2, m.v3],
                [m.v1],
                ]
        for i, (block, inputs) in enumerate(generate_subsystem_blocks(subs)):
            inputs = list(block.input_vars.values())
            with TemporarySubsystemManager(to_fix=inputs):
                self.assertIs(block.model(), block)
                var_set = ComponentSet(subs[i][1])
                con_set = ComponentSet(subs[i][0])
                input_set = ComponentSet(other_vars[i])

                self.assertEqual(len(var_set), len(block.vars))
                self.assertEqual(len(con_set), len(block.cons))
                self.assertEqual(len(input_set), len(inputs))
                self.assertTrue(all(var in var_set for var in block.vars[:]))
                self.assertTrue(all(con in con_set for con in block.cons[:]))
                self.assertTrue(all(var in input_set for var in inputs))
                self.assertTrue(all(var.fixed for var in inputs))
                self.assertFalse(any(var.fixed for var in block.vars[:]))

        # Test that we have properly unfixed variables, except variables
        # that were already fixed.
        self.assertFalse(m.v1.fixed)
        self.assertFalse(m.v2.fixed)
        self.assertFalse(m.v3.fixed)
        self.assertTrue(m.v4.fixed)
Beispiel #2
0
    def test_fix_then_solve(self):
        # This is a test of the expected use case. We have a (square)
        # subsystem that we can solve easily after fixing and deactivating
        # certain variables and constraints.

        m = _make_simple_model()
        ipopt = pyo.SolverFactory("ipopt")

        # Initialize to avoid converging infeasible due to bad pivots
        m.v1.set_value(1.0)
        m.v2.set_value(1.0)
        m.v3.set_value(1.0)
        m.v4.set_value(2.0)

        with TemporarySubsystemManager(to_fix=[m.v3, m.v4],
                                       to_deactivate=[m.con1]):
            # Solve the subsystem with m.v1, m.v2 unfixed and
            # m.con2, m.con3 inactive.
            ipopt.solve(m)

        # 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)
Beispiel #3
0
def initialize_by_time_element(
    m,
    time,
    solver,
    solve_kwds=None,
    skip_partition=False,
    flatten_vars=None,
    flatten_cons=None,
    time_subsystems=None,
):
    if solve_kwds is None:
        solve_kwds = {}
    reslist = []
    for block, inputs in generate_time_element_blocks(
            m,
            time,
            skip_partition=skip_partition,
            flatten_vars=flatten_vars,
            flatten_cons=flatten_cons,
            time_subsystems=time_subsystems,
    ):
        with TemporarySubsystemManager(to_fix=inputs):
            with TIMER.context("solve"):
                res = solver.solve(block, **solve_kwds)
            reslist.append(res)
    return reslist
Beispiel #4
0
    def test_generate_subsystems_include_fixed_var(self):
        m = _make_simple_model()
        m.v4.fix()
        subsystems = [
                ([m.con1], [m.v1]),
                ([m.con2, m.con3], [m.v2, m.v3]),
                ]
        other_vars = [
                [m.v2, m.v3, m.v4],
                [m.v1, m.v4],
                ]
        for i, (block, inputs) in enumerate(generate_subsystem_blocks(
            subsystems,
            include_fixed=True,
            )):
            with TemporarySubsystemManager(to_fix=inputs):
                self.assertIs(block.model(), block)
                var_set = ComponentSet(subsystems[i][1])
                con_set = ComponentSet(subsystems[i][0])
                input_set = ComponentSet(other_vars[i])
                
                self.assertEqual(len(var_set), len(block.vars))
                self.assertEqual(len(con_set), len(block.cons))
                self.assertEqual(len(input_set), len(block.input_vars))
                self.assertTrue(all(var in var_set for var in block.vars[:]))
                self.assertTrue(all(con in con_set for con in block.cons[:]))
                self.assertTrue(all(var in input_set for var in inputs))
                self.assertTrue(all(var.fixed for var in inputs))
                self.assertFalse(any(var.fixed for var in block.vars[:]))

        self.assertFalse(m.v1.fixed)
        self.assertFalse(m.v2.fixed)
        self.assertFalse(m.v3.fixed)
        self.assertTrue(m.v4.fixed)
Beispiel #5
0
    def test_context_some_redundant(self):
        m = _make_simple_model()

        to_fix = [m.v2, m.v4]
        to_deactivate = [m.con1, m.con2]
        to_reset = [m.v1]

        m.v1.set_value(1.5)
        m.v2.fix()
        m.con1.deactivate()

        with TemporarySubsystemManager(to_fix, to_deactivate, to_reset):
            self.assertEqual(m.v1.value, 1.5)
            self.assertTrue(m.v2.fixed)
            self.assertTrue(m.v4.fixed)
            self.assertFalse(m.con1.active)
            self.assertFalse(m.con2.active)

            m.v1.set_value(2.0)
            m.v2.set_value(3.0)

        self.assertEqual(m.v1.value, 1.5)
        self.assertEqual(m.v2.value, 3.0)
        self.assertTrue(m.v2.fixed)
        self.assertFalse(m.v4.fixed)
        self.assertTrue(m.con2.active)
        self.assertFalse(m.con1.active)
Beispiel #6
0
    def test_generate_subsystems_without_fixed_var(self):
        m = _make_simple_model()
        subs = [
                ([m.con1], [m.v1, m.v4]),
                ([m.con2, m.con3], [m.v2, m.v3]),
                ]
        other_vars = [
                [m.v2, m.v3],
                [m.v1, m.v4],
                ]
        for i, (block, inputs) in enumerate(generate_subsystem_blocks(subs)):
            with TemporarySubsystemManager(to_fix=inputs):
                self.assertIs(block.model(), block)
                var_set = ComponentSet(subs[i][1])
                con_set = ComponentSet(subs[i][0])
                input_set = ComponentSet(other_vars[i])
                
                self.assertEqual(len(var_set), len(block.vars))
                self.assertEqual(len(con_set), len(block.cons))
                self.assertEqual(len(input_set), len(block.input_vars))
                self.assertTrue(all(var in var_set for var in block.vars[:]))
                self.assertTrue(all(con in con_set for con in block.cons[:]))
                self.assertTrue(all(var in input_set for var in inputs))
                self.assertTrue(all(var.fixed for var in inputs))
                self.assertFalse(any(var.fixed for var in block.vars[:]))

        # Test that we have properly unfixed variables
        self.assertFalse(any(var.fixed for var in
            m.component_data_objects(pyo.Var)))
def get_polynomial_degree_wrt(constraints, variables=None):
    # TODO: Move this function elsewhere
    from pyomo.core.expr.visitor import polynomial_degree
    from pyomo.util.subsystems import TemporarySubsystemManager
    vars_in_cons = list(_generate_variables_in_constraints(constraints))
    if variables is None:
        variables = vars_in_cons
    var_set = ComponentSet(variables)
    other_vars = [v for v in vars_in_cons if v not in var_set]
    with TemporarySubsystemManager(to_fix=other_vars):
        con_poly_degree = [polynomial_degree(con.expr) for con in constraints]
    if any(d is None for d in con_poly_degree):
        # General nonlinear
        return None
    else:
        return max(con_poly_degree)
Beispiel #8
0
def initialize_steady(m):
    """
    This is my approach for initializing the steady state model.
    Set state variables to their inlet values, except gas temperature,
    which is initialized to the temperature of the solid inlet.
    Then deactivate discretization equations and strongly connected
    component decomposition.
    """
    gas_inlet_names = set_gas_values_to_inlets(m)
    solid_inlet_names = set_solid_values_to_inlets(m)
    set_gas_temperature_to_solid_inlet(m)

    gas_phase = m.fs.MB.gas_phase
    solid_phase = m.fs.MB.solid_phase
    gas_length = m.fs.MB.gas_phase.length_domain
    solid_length = m.fs.MB.solid_phase.length_domain
    gas_disc_eqs = [
        con for _, con, _ in generate_discretization_components_along_set(
            m, gas_length)
    ]
    solid_disc_eqs = [
        con for _, con, _ in generate_discretization_components_along_set(
            m, solid_length)
    ]

    gas_sum_eqn_slice = gas_phase.properties[:, :].sum_component_eqn
    gas_sum_eqn_slice.attribute_errors_generate_exceptions = False
    solid_sum_eqn_slice = solid_phase.properties[:, :].sum_component_eqn
    solid_sum_eqn_slice.attribute_errors_generate_exceptions = False

    to_deactivate = []
    to_deactivate.extend(gas_sum_eqn_slice)
    to_deactivate.extend(solid_sum_eqn_slice)
    to_deactivate.extend(gas_disc_eqs)
    to_deactivate.extend(solid_disc_eqs)

    to_fix = []
    for name in gas_inlet_names + solid_inlet_names:
        ref = m.find_component(name)
        to_fix.extend(ref.values())

    with TemporarySubsystemManager(to_fix=to_fix, to_deactivate=to_deactivate):
        solve_strongly_connected_components(m)
Beispiel #9
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)))
Beispiel #10
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)
Beispiel #11
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)
Beispiel #12
0
def initialize_steady_without_solid_temperature(m):
    """
    """
    gas_inlet_names = set_gas_values_to_inlets(m)
    solid_inlet_names = set_solid_values_to_inlets(m)

    gas_phase = m.fs.MB.gas_phase
    solid_phase = m.fs.MB.solid_phase
    gas_length = m.fs.MB.gas_phase.length_domain
    solid_length = m.fs.MB.solid_phase.length_domain
    gas_disc_eqs = [
        con for _, con, _ in generate_discretization_components_along_set(
            m, gas_length)
    ]
    solid_disc_eqs = [
        con for _, con, _ in generate_discretization_components_along_set(
            m, solid_length)
    ]

    gas_sum_eqn_slice = gas_phase.properties[:, :].sum_component_eqn
    gas_sum_eqn_slice.attribute_errors_generate_exceptions = False
    solid_sum_eqn_slice = solid_phase.properties[:, :].sum_component_eqn
    solid_sum_eqn_slice.attribute_errors_generate_exceptions = False

    to_deactivate = []
    to_deactivate.extend(gas_sum_eqn_slice)
    to_deactivate.extend(solid_sum_eqn_slice)
    to_deactivate.extend(gas_disc_eqs)
    to_deactivate.extend(solid_disc_eqs)

    to_fix = []
    for name in gas_inlet_names + solid_inlet_names:
        ref = m.find_component(name)
        to_fix.extend(ref.values())

    with TemporarySubsystemManager(to_fix=to_fix, to_deactivate=to_deactivate):
        solve_strongly_connected_components(m)
    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)

        for block, inputs in self._scc_list:
            if len(block.vars) == 1:
                calculate_variable_from_constraint(block.vars[0],
                                                   block.cons[0])
            else:
                with TemporarySubsystemManager(to_fix=inputs):
                    solver.solve(block)

        # Send updated variable values to NLP for dervative evaluation
        primals = self._nlp.get_primals()
        to_update = input_vars + external_vars
        indices = self._nlp.get_primal_indices(to_update)
        values = np.fromiter((var.value for var in to_update), float)
        primals[indices] = values
        self._nlp.set_primals(primals)
Beispiel #14
0
def main():
    """
    """
    nxfe = 10 # Default = 10
    # NOTE: Default inputs: (128.2, 591.4)
    ic_model_params = {"nxfe": nxfe}
    ic_inputs = {
        #"fs.MB.gas_phase.properties[*,0.0].flow_mol": 120.0,
        #"fs.MB.solid_phase.properties[*,1.0].flow_mass": 550.0,
    }
    m_ic = get_steady_state_model(
        ic_inputs,
        solve_kwds={"tee": True},
        model_params=ic_model_params,
    )
    time = m_ic.fs.time
    scalar_data, dae_data = get_data_from_steady_model(m_ic, time)
    # TODO: get steady state data (scalar and dae both necessary)
    x0 = 0.0
    x1 = 1.0

    # NOTE: Decreasing size of model (horizon, tfe_width, and nxfe)
    # while developing NMPC workflow. Defaults: 900, 15, 10
    n_nmpc_samples = 30 # For now. My target should probably be around 20-30
    horizon = 1800
    tfe_width = 60
    sample_width = 180
    sample_points = [
        # Calculate sample points first with integer arithmetic
        # to avoid roundoff error
        float(sample_width*i) for i in range(0, horizon//sample_width + 1)
    ]
    nmpc_sample_points = [
        float(sample_width*i) for i in range(n_nmpc_samples)
        # This is the "real time" at which NMPC inputs are applied
        # for each solve of the optimal control problem.
    ]
    horizon = float(horizon)
    tfe_width = float(tfe_width)
    model_params = {
        "horizon": horizon,
        "tfe_width": tfe_width,
        "ntcp": 1,
        "nxfe": nxfe,
    }

    # These are approximately the default values:
    #disturbance_dict = {"CO2": 0.03, "H2O": 0.0, "CH4": 0.97}
    disturbance_dict = {"CO2": 0.5, "H2O": 0.0, "CH4": 0.5}
    disturbance = dict(
        (
            "fs.MB.gas_phase.properties[*,%s].mole_frac_comp[%s]" % (x0, j),
            {(0.0, horizon): val},
        )
        for j, val in disturbance_dict.items()
    )

    # Create solver here as it is needed to solve for the setpoint
    solver = pyo.SolverFactory("ipopt")
    solver.options["linear_solver"] = "ma57"
    solver.options["max_cpu_time"] = 900

    #
    # Get setpoint data
    #
    sp_inputs = get_inputs_at_time(disturbance, horizon)
    #sp_inputs.update({
    #    "fs.MB.gas_phase.properties[*,0.0].flow_mol": 272.8,
    #    "fs.MB.solid_phase.properties[*,1.0].flow_mass": 591.4,
    #})
    sp_model_params = {"nxfe": nxfe}
    m_sp = get_steady_state_model(
        sp_inputs,
        solve_kwds={"tee": True},
        model_params=sp_model_params,
    )
    time = m_sp.fs.time
    space = m_sp.fs.MB.gas_phase.length_domain
    t0 = time.first()
    # Solve optimization problem for setpoint
    sp_objective_states = [
        "fs.MB.solid_phase.reactions[*,%s].OC_conv" % x0,
    ]
    sp_target = {
        "fs.MB.solid_phase.reactions[*,%s].OC_conv" % x0: 0.95,
    }
    m_sp.fs.MB.gas_inlet.flow_mol[:].unfix()
    m_sp.setpoint_expr = get_tracking_cost_expressions(
        sp_objective_states, time, sp_target
    )
    m_sp.objective = pyo.Objective(expr=m_sp.setpoint_expr[t0])
    solver.solve(m_sp, tee=True)
    scalar_vars, dae_vars = flatten_dae_components(m_sp, time, pyo.Var)
    setpoint = {
        str(pyo.ComponentUID(var.referent)): var[t0].value
        for var in dae_vars
    }
    ###

    max_data = get_max_values_from_steady(m_sp)
    variance_data = get_variance_of_time_slices(m_sp, time, space)
    weight_data = None
    #weight_data = {
    #    name: 1.0/s if s != 0 else 1.0 for name, s in variance_data.items()
    #    #name: 1/w if w != 0 else 1.0 for name, w in max_data.items()
    #    # Note: 1/w**2 does not converge with states in objective...
    #}
    objective_states = get_state_variable_names(space)

    with open("ic_data.json", "w") as fp:
        json.dump(dae_data, fp)
    with open("setpoint_data.json", "w") as fp:
        json.dump(setpoint, fp)

    flattened_vars = [None, None]
    m = get_model_for_dynamic_optimization(
        sample_points=sample_points,
        parameter_perturbation=disturbance,
        model_params=model_params,
        ic_scalar_data=scalar_data,
        ic_dae_data=dae_data,
        setpoint_data=setpoint,
        objective_weights=weight_data,
        objective_states=objective_states,

        # this argument is a huge hack to get the flattened
        # vars without having to do a bit more work.
        flatten_out=flattened_vars,
    )
    add_constraints_for_missing_variables(m)
    time = m.fs.time
    t0 = time.first()
    scalar_vars, dae_vars = flattened_vars
    initialize_dynamic(m, dae_vars)

    #TODO:
    # Outside the loop:
    # - make plant model
    # - initialize data structure for plant data to initial condition
    #   (one data structure for states, one for controls)
    # Inside the loop:
    # - initialize controller model (with bounds fixed)
    # - solve control problem
    # - extract first control input from controller model,
    #   send to plant and data structure for inputs
    # - simulate plant
    # - extend plant state data structure with results of simulation
    # - update controller initial conditions with final value from plant
    # - update plant initial conditions with final value from plant

    plant_model_params = {
        "horizon": sample_width,
        "tfe_width": tfe_width,
        "ntcp": 1,
        "nxfe": nxfe,
    }
    m_plant = get_nmpc_plant_model(
        parameter_perturbation=disturbance,
        model_params=plant_model_params,
        ic_scalar_data=scalar_data,
        ic_dae_data=dae_data,
        setpoint_data=setpoint,
        objective_weights=weight_data,
        objective_states=objective_states,

        # this argument is a huge hack to get the flattened
        # vars without having to do a bit more work.
        flatten_out=flattened_vars,
    )
    add_constraints_for_missing_variables(m_plant)
    plant_time = m_plant.fs.time

    input_names = [
        "fs.MB.gas_phase.properties[*,0.0].flow_mol",
        "fs.MB.solid_phase.properties[*,1.0].flow_mass",
    ]
    applied_inputs = (
        [t0],
        {name: [m.find_component(name)[t0].value] for name in input_names},
    )
    # TODO: Initialize a planned_inputs sequence

    plant_scalar_vars, plant_dae_vars = flattened_vars
    plant_variables = list(plant_dae_vars)
    plant_variables.append(m_plant.tracking_cost)
    plant_data = initialize_time_series_data(plant_variables, plant_time, t0=t0)
    controller_dae_vars = [
        m.find_component(var.referent) for var in plant_dae_vars
    ]
    controller_variables = list(controller_dae_vars)
    controller_variables.append(m.tracking_cost)

    # Assuming plant and controller have same initial conditions at this point
    for i in range(n_nmpc_samples):
        current_time = nmpc_sample_points[i]
        #
        # Initialze controller
        #
        input_vardata = (
            [m.fs.MB.gas_inlet.flow_mol[t] for t in time if t != t0]
            + [m.fs.MB.solid_inlet.flow_mass[t] for t in time if t != t0]
        )
        # Want an unbounded conversion for simulation.
        m.fs.MB.solid_phase.reactions[:,0.0].OC_conv.setlb(None)
        with TemporarySubsystemManager(
                to_fix=input_vardata,
                to_deactivate=[m.piecewise_constant_constraint],
                ):
            print("Initializing controller by time element...")
            with TIMER.context("elem-init-controller"):
                initialize_by_time_element(m, time, solver)
        m.fs.MB.solid_phase.reactions[:,0.0].OC_conv.setlb(0.89)
        ###

        #
        # Solve controller model
        #
        print("Starting dynamic optimization solve...")
        with TIMER.context("solve dynamic"):
            solver.solve(m, tee=True)
        ###

        #
        # Extract inputs from controller model
        #
        controller_inputs = (
            list(sample_points),
            {
                name: [m.find_component(name)[ts].value for ts in sample_points]
                for name in input_names
            },
        )
        # We have two important input sequences to keep track of. One is the
        # past inputs, actually applied to the plant. The other is the planned
        # inputs.
        # TODO, here:
        # (a) Extract the first input from controller_inputs, use it to extend
        #     the sequence of applied inputs
        # (b) Apply offset to this controller_inputs sequence, rename it
        #     planned_inputs
        # Both of these have an offset applied
        # (c) Should happen first: extract first input from controller_inputs,
        #     apply to plant

        # This is the extracted first input
        plant_inputs = (
            [t0, sample_width],
            {
                name: [values[0], values[1]]
                for name, values in controller_inputs[1].items()
            },
        )

        # Add this extracted first input to the sequence of applied inputs
        applied_inputs[0].append(current_time + sample_width)
        for name, values in plant_inputs[1].items():
            applied_inputs[1][name].append(values[1])

        # Planned_inputs. These will be used in the case we cannot solve
        # a dynamic optimization problem.
        planned_inputs = (
            [t + current_time for t in controller_inputs[0]],
            controller_inputs[1],
        )

        #
        # Sent inputs into plant model
        #
        plant_inputs = interval_data_from_time_series(plant_inputs)
        load_inputs_into_model(m_plant, time, plant_inputs)
        ###

        #
        # Simulate plant model
        #
        print("Initializing plant by time element...")
        with TIMER.context("elem-init-plant"):
            initialize_by_time_element(m_plant, plant_time, solver)
            solver.solve(m_plant, tee=True)
        # Record data
        plant_data = extend_time_series_data(
            plant_data,
            plant_variables,
            plant_time,
            offset=current_time,
        )
        ###

        pyo.Reference(m.fs.MB.solid_phase.properties[:, 0.0].temperature).pprint()
        # indiscriminately shift every time-indexed variable in the
        # model backwards one sample. Inputs, disturbances, everything...
        seen = set()
        for var in controller_dae_vars:
            if id(var[t0]) in seen:
                continue
            else:
                # We need to make sure we don't do this twice for the same
                # vardata. Note that we can encounter the same vardata multiple
                # times due to references.
                seen.add(id(var[t0]))
            for t in time:
                ts = t + sample_width
                idx = time.find_nearest_index(ts, tolerance=1e-8)
                if idx is None:
                    # ts is outside the controller's horizon
                    var[t].set_value(var[time.last()].value)
                else:
                    ts = time.at(idx)
                    var[t].set_value(var[ts].value)
        pyo.Reference(m.fs.MB.solid_phase.properties[:, 0.0].temperature).pprint()

        #
        # Re-initialize plant and controller to new initial conditions
        #
        tf = sample_width
        for i, var in enumerate(plant_dae_vars):
            final_value = var[tf].value
            for t in plant_time:
                var[t].set_value(final_value)
            controller_var = controller_dae_vars[i]
            controller_var[t0].set_value(final_value)
        ###

    plant_fname = "nmpc_plant_data.json"
    with open(plant_fname, "w") as fp:
        json.dump(plant_data, fp)
    # Note that this is not actually all the data I need.
    # I also need the setpoint data.

    input_fname = "nmpc_input_data.json"
    with open(input_fname, "w") as fp:
        json.dump(applied_inputs, fp)
Beispiel #15
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, skip_validation=True)

        vector_scc_idx = 0
        for block, inputs in self._scc_list:
            if len(block.vars) == 1:
                calculate_variable_from_constraint(block.vars[0],
                                                   block.cons[0])
            else:
                if self._use_cyipopt:
                    # Transfer variable values into the projected NLP, solve,
                    # and extract values.

                    nlp = self._vector_scc_nlps[vector_scc_idx]
                    proj_nlp = self._vector_proj_nlps[vector_scc_idx]
                    input_coords = self._vector_scc_input_coords[
                        vector_scc_idx]
                    cyipopt = self._cyipopt_solvers[vector_scc_idx]
                    _, local_inputs = self._vector_scc_list[vector_scc_idx]

                    primals = nlp.get_primals()
                    variables = nlp.get_pyomo_variables()

                    # Set values and bounds from inputs to the SCC.
                    # This works because values have been set in the original
                    # pyomo model, either by a previous SCC solve, or from the
                    # "global inputs"
                    for i, var in zip(input_coords, local_inputs):
                        # Set primals (inputs) in the original NLP
                        primals[i] = var.value
                    # This affects future evaluations in the ProjectedNLP
                    nlp.set_primals(primals)
                    x0 = proj_nlp.get_primals()
                    sol, _ = cyipopt.solve(x0=x0)

                    # Set primals from solution in projected NLP. This updates
                    # values in the original NLP
                    proj_nlp.set_primals(sol)
                    # I really only need to set new primals for the variables in
                    # the ProjectedNLP. However, I can only get a list of variables
                    # from the original Pyomo NLP, so here some of the values I'm
                    # setting are redundant.
                    new_primals = nlp.get_primals()
                    assert len(new_primals) == len(variables)
                    for var, val in zip(variables, new_primals):
                        var.set_value(val, skip_validation=True)

                else:
                    # Use a Pyomo solver to solve this strongly connected
                    # component.
                    with TemporarySubsystemManager(to_fix=inputs):
                        solver.solve(block)

                vector_scc_idx += 1

        # Send updated variable values to NLP for dervative evaluation
        primals = self._nlp.get_primals()
        to_update = input_vars + external_vars
        indices = self._nlp.get_primal_indices(to_update)
        values = np.fromiter((var.value for var in to_update), float)
        primals[indices] = values
        self._nlp.set_primals(primals)
Beispiel #16
0
def main():
    """
    """
    nxfe = 10
    # NOTE: Default inputs: (128.2, 591.4)
    ic_inputs = {
        #"fs.MB.gas_phase.properties[*,0.0].flow_mol": 120.0,
        #"fs.MB.solid_phase.properties[*,1.0].flow_mass": 550.0,
    }
    ic_model_params = {"nxfe": nxfe}
    m_ic = get_steady_state_model(
        ic_inputs,
        solve_kwds={"tee": True},
        model_params=ic_model_params,
    )
    time = m_ic.fs.time
    scalar_data, dae_data = get_data_from_steady_model(m_ic, time)
    # TODO: get steady state data (scalar and dae both necessary)
    x0 = 0.0
    x1 = 1.0

    # These (as well as nxfe above) are the parameters for a small model
    # that I'm using to test NMPC (defaults are 900, 15, 60), nxfe=10
    horizon = 1800
    tfe_width = 60
    sample_width = 120
    sample_points = [
        # Calculate sample points first with integer arithmetic
        # to avoid roundoff error
        float(sample_width*i) for i in range(0, horizon//sample_width + 1)
    ]
    horizon = float(horizon)
    tfe_width = float(tfe_width)
    model_params = {
        "horizon": horizon,
        "tfe_width": tfe_width,
        "ntcp": 1,
        "nxfe": nxfe,
    }

    # These are approximately the default values:
    #disturbance_dict = {"CO2": 0.03, "H2O": 0.0, "CH4": 0.97}
    disturbance_dict = {"CO2": 0.5, "H2O": 0.0, "CH4": 0.5}
    disturbance = dict(
        (
            "fs.MB.gas_phase.properties[*,%s].mole_frac_comp[%s]" % (x0, j),
            {(0.0, horizon): val},
        )
        for j, val in disturbance_dict.items()
    )

    # Create solver here as it is needed to solve for the setpoint
    solver = pyo.SolverFactory("ipopt")
    solver.options["linear_solver"] = "ma57"
    solver.options["max_cpu_time"] = 1500

    #
    # Get setpoint data
    #
    sp_inputs = get_inputs_at_time(disturbance, horizon)
    #sp_inputs.update({
    #    "fs.MB.gas_phase.properties[*,0.0].flow_mol": 272.8,
    #    "fs.MB.solid_phase.properties[*,1.0].flow_mass": 591.4,
    #})
    sp_model_params = {"nxfe": nxfe}
    m_sp = get_steady_state_model(
        sp_inputs,
        solve_kwds={"tee": True},
        model_params=sp_model_params,
    )
    time = m_sp.fs.time
    space = m_sp.fs.MB.gas_phase.length_domain
    t0 = time.first()
    # Solve optimization problem for setpoint
    sp_objective_states = [
        "fs.MB.solid_phase.reactions[*,%s].OC_conv" % x0,
    ]
    sp_target = {
        "fs.MB.solid_phase.reactions[*,%s].OC_conv" % x0: 0.95,
    }
    m_sp.fs.MB.gas_inlet.flow_mol[:].unfix()
    m_sp.setpoint_expr = get_tracking_cost_expressions(
        sp_objective_states, time, sp_target
    )
    m_sp.objective = pyo.Objective(expr=m_sp.setpoint_expr[t0])
    solver.solve(m_sp, tee=True)
    scalar_vars, dae_vars = flatten_dae_components(m_sp, time, pyo.Var)
    setpoint = {
        str(pyo.ComponentUID(var.referent)): var[t0].value
        for var in dae_vars
    }
    ###

    max_data = get_max_values_from_steady(m_sp)
    variance_data = get_variance_of_time_slices(m_sp, time, space)
    #weight_data = None
    weight_data = {
        name: 1.0/s if s != 0 else 1.0 for name, s in variance_data.items()
        #name: 1/w if w != 0 else 1.0 for name, w in max_data.items()
        # Note: 1/w**2 does not converge with states in objective...
    }
    objective_states = get_state_variable_names(space)

    flattened_vars = [None, None]
    m = get_model_for_dynamic_optimization(
        sample_points=sample_points,
        parameter_perturbation=disturbance,
        model_params=model_params,
        ic_scalar_data=scalar_data,
        ic_dae_data=dae_data,
        setpoint_data=setpoint,
        objective_weights=weight_data,
        objective_states=objective_states,

        # this argument is a huge hack to get the flattened
        # vars without having to do a bit more work.
        flatten_out=flattened_vars,
    )
    add_constraints_for_missing_variables(m)
    time = m.fs.time
    t0 = time.first()
    scalar_vars, dae_vars = flattened_vars
    initialize_dynamic(m, dae_vars)

    # Should we initialize to setpoint inputs?
    #sp_input_dict = {
    #    "fs.MB.gas_phase.properties[*,0.0].flow_mol": {(t0, horizon): 250.0},
    #    "fs.MB.solid_phase.properties[*,1.0].flow_mass": {(t0, horizon): 591.4},
    #}
    #load_inputs_into_model(m, time, sp_input_dict)

    # TODO: Should I set inlet flow rates to their target values for
    # this simulation?
    input_vardata = (
        [m.fs.MB.gas_inlet.flow_mol[t] for t in time if t != t0]
        + [m.fs.MB.solid_inlet.flow_mass[t] for t in time if t != t0]
    )
    with TemporarySubsystemManager(
            to_fix=input_vardata,
            to_deactivate=[m.piecewise_constant_constraint],
            ):
        print("Initializing by time element...")
        with TIMER.context("elem-init"):
            initialize_by_time_element(m, time, solver)

    m.fs.MB.solid_phase.reactions[:,0.0].OC_conv.setlb(0.89)

    print("Starting dynamic optimization solve...")
    with TIMER.context("solve dynamic"):
        solver.solve(m, tee=True)

    extra_states = [
        pyo.Reference(m.fs.MB.solid_phase.reactions[:,0.0].OC_conv),
    ]
    plot_outlet_states_over_time(m, show=False, extra_states=extra_states)
    inputs = [
        "fs.MB.gas_phase.properties[*,0.0].flow_mol",
        "fs.MB.solid_phase.properties[*,1.0].flow_mass",
    ]
    plot_inputs_over_time(m, inputs, show=False)
    print(m.tracking_cost.name)
    for t in m.fs.time:
        print(t, pyo.value(m.tracking_cost[t]))
    print()
Beispiel #17
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
Beispiel #18
0
def solve_strongly_connected_components(block, solver=None, solve_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.

    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

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

    """
    if solve_kwds is None:
        solve_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:
                calculate_variable_from_constraint(scc.vars[0], scc.cons[0])
            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