예제 #1
0
def test_categorize_error():
    model = make_model(horizon=1, ntfe=5, ntcp=2)
    time = model.fs.time

    scalar_vars, dae_vars = flatten_dae_components(model, time, ctype=Var)

    # Add a dummy var to treat as an input.
    # This var is not in `dae_vars`, so it will not be located during
    # categorization, which should fail.
    model.dummy_var = Var(time)

    init_input_list = [
        model.fs.mixer.S_inlet.flow_vol[0],
        model.fs.mixer.E_inlet.flow_vol[0],
        model.dummy_var[0],
    ]

    with pytest.raises(RuntimeError, match=r'Not all inputs could be found'):
        category_dict = categorize_dae_variables(
            dae_vars,
            time,
            init_input_list,
        )

    # Re-run flattener. Now `dummy_var` should be included in `dae_vars`.
    scalar_vars, dae_vars = flatten_dae_components(model, time, ctype=Var)
    category_dict = categorize_dae_variables(
        dae_vars,
        time,
        init_input_list,
    )
예제 #2
0
def get_subsystems_along_time(
    m,
    time,
    flatten_vars=None,
    flatten_cons=None,
):
    """
    Arguments
    ---------
    m: Block
        A model containing components indexed by time
    time: Set
        A set indexing components of the model

    """
    with TIMER.context("flatten"):
        if flatten_vars is None:
            scalar_vars, dae_vars = flatten_dae_components(m, time, Var)
        else:
            scalar_vars, dae_vars = flatten_vars
        if flatten_cons is None:
            scalar_cons, dae_cons = flatten_dae_components(m, time, Constraint)
        else:
            scalar_cons, dae_cons = flatten_cons

    with TIMER.context("identify_variables"):
        con_vars = dict()
        for t in time:
            con_vars[t] = ComponentSet()
            for con in dae_cons:
                if t in con and con.active and con[t].active:
                    for var in identify_variables(
                            con[t].expr,
                            include_fixed=False,
                    ):
                        con_vars[t].add(var)

    with TIMER.context("filter"):
        subsystems = [(
            list(
                _filter_duplicates(
                    con[t] for con in dae_cons
                    if t in con and con.active and con[t].active)),
            list(
                _filter_duplicates(var[t] for var in dae_vars
                                   if t in var and var[t] in con_vars[t])),
        ) for t in time]
    return subsystems
예제 #3
0
def initialize_dynamic_from_steady(m_dyn, m_steady, flattened=None):
    """
    """
    time = m_dyn.fs.time
    time_steady = m_steady.fs.time
    t_steady = time_steady.first()

    TIMER.start("get derivs")
    diff_deriv_disc_list = list(
        generate_diff_deriv_disc_components_along_set(m_dyn, time))
    derivs = [var for _, var, _ in diff_deriv_disc_list]
    TIMER.stop("get derivs")

    TIMER.start("flatten dynamic")
    if flattened is None:
        scalar_vars, dae_vars = flatten_dae_components(m_dyn, time, pyo.Var)
    else:
        scalar_vars, dae_vars = flattened
    TIMER.stop("flatten dynamic")
    TIMER.start("flatten steady")
    steady_scalar_vars, steady_dae_vars = flatten_dae_components(
        m_steady, time_steady, pyo.Var)
    TIMER.stop("flatten steady")

    deriv_cuid_set = set(str(pyo.ComponentUID(var.referent)) for var in derivs)
    scalar_value_map = dict(
        (str(pyo.ComponentUID(var)), var.value) for var in steady_scalar_vars)
    dae_value_map = dict(
        (str(pyo.ComponentUID(var.referent)), var[t_steady].value)
        for var in steady_dae_vars)

    TIMER.start("set values")
    for var in scalar_vars:
        cuid = str(pyo.ComponentUID(var))
        var.set_value(scalar_value_map[cuid])
    for var in dae_vars:
        cuid = str(pyo.ComponentUID(var.referent))
        if cuid in dae_value_map:
            for t in time:
                var[t].set_value(dae_value_map[cuid])
        else:
            assert cuid in deriv_cuid_set
            # TODO: Better way of initializing derivatives
            for t in time:
                var[t].set_value(0.0)
    TIMER.stop("set values")

    return scalar_vars, dae_vars
예제 #4
0
 def make_block(self, sample_time=0.5, horizon=1, nfe=2):
     model = make_model(horizon=horizon, nfe=nfe)
     time = model.time
     t0 = time.first()
     inputs = [model.flow_in[t0]]
     measurements = [model.conc[t0, 'A'], model.conc[t0, 'B']]
     scalar_vars, dae_vars = flatten_dae_components(
         model,
         time,
         pyo.Var,
     )
     category_dict = categorize_dae_variables(dae_vars,
                                              time,
                                              inputs,
                                              measurements=measurements)
     dyn_block = DynamicBlock(
         model=model,
         time=time,
         category_dict={None: category_dict},
         #inputs=inputs,
         #measurements=measurements,
     )
     dyn_block.construct()
     dyn_block.set_sample_time(sample_time)
     return dyn_block
예제 #5
0
    def test_flat_model(self):
        m = ConcreteModel()
        m.T = ContinuousSet(bounds=(0, 1))
        m.x = Var()
        m.y = Var([1, 2])
        m.a = Var(m.T)
        m.b = Var(m.T, [1, 2])
        m.c = Var([3, 4], m.T)

        regular, time = flatten_dae_components(m, m.T, Var)
        regular_id = set(id(_) for _ in regular)
        self.assertEqual(len(regular), 3)
        self.assertIn(id(m.x), regular_id)
        self.assertIn(id(m.y[1]), regular_id)
        self.assertIn(id(m.y[2]), regular_id)
        # Output for debugging
        #for v in time:
        #    v.pprint()
        #    for _ in v.values():
        #        print"     -> ", _.name
        ref_data = {
            self._hashRef(Reference(m.a[:])),
            self._hashRef(Reference(m.b[:, 1])),
            self._hashRef(Reference(m.b[:, 2])),
            self._hashRef(Reference(m.c[3, :])),
            self._hashRef(Reference(m.c[4, :])),
        }
        self.assertEqual(len(time), len(ref_data))
        for ref in time:
            self.assertIn(self._hashRef(ref), ref_data)
예제 #6
0
    def _construct(self):
        """ Generates time-indexed references and categorizes them. """
        model = self.mod
        time = self.time
        inputs = self._inputs
        try:
            measurements = self._measurements
        except AttributeError:
            measurements = self._measurements = None

        # TODO: Give the user the option to provide their own
        # category_dict (they know the structure of their model
        # better than I do...)
        scalar_vars, dae_vars = flatten_dae_components(
            model,
            time,
            ctype=Var,
        )
        self.scalar_vars = scalar_vars
        self.dae_vars = dae_vars
        category_dict = categorize_dae_variables(
            dae_vars,
            time,
            inputs,
            measurements=measurements,
        )
        self.category_dict = category_dict

        self._add_category_blocks()
        self._add_category_references()

        self.differential_vars = category_dict[VC.DIFFERENTIAL]
        self.algebraic_vars = category_dict[VC.ALGEBRAIC]
        self.derivative_vars = category_dict[VC.DERIVATIVE]
        self.input_vars = category_dict[VC.INPUT]
        self.fixed_vars = category_dict[VC.FIXED]

        self.measurement_vars = category_dict.pop(VC.MEASUREMENT)
        # The categories in category_dict now form a partition of the
        # time-indexed variables. This is necessary to have a well-defined
        # vardata map, which maps each vardata to a unique component indexed
        # only by time.

        # Maps each vardata (of a time-indexed var) to the NmpcVar
        # that contains it.
        self.vardata_map = ComponentMap((var[t], var)
                                        for varlist in category_dict.values()
                                        for var in varlist for t in time)
        # NOTE: looking up var[t] instead of iterating over values()
        # appears to be ~ 5x faster

        # These should be overridden by a call to `set_sample_time`
        # The defaults assume that the entire model is one sample.
        self.sample_points = [time.first(), time.last()]
        self.sample_point_indices = [1, len(time)]
예제 #7
0
def test_initialize_by_element_in_range():
    mod = make_model(horizon=2, ntfe=20)
    assert degrees_of_freedom(mod) == 0

    scalar_vars, dae_vars = flatten_dae_components(mod.fs, mod.fs.time, Var)
    diff_vars = [
        Reference(mod.fs.cstr.control_volume.energy_holdup[:, 'aq']),
        Reference(mod.fs.cstr.control_volume.material_holdup[:, 'aq', 'S']),
        Reference(mod.fs.cstr.control_volume.material_holdup[:, 'aq', 'E']),
        Reference(mod.fs.cstr.control_volume.material_holdup[:, 'aq', 'C']),
        Reference(mod.fs.cstr.control_volume.material_holdup[:, 'aq', 'P'])
    ]

    initialize_by_element_in_range(mod.fs,
                                   mod.fs.time,
                                   0,
                                   1,
                                   solver=solver,
                                   dae_vars=dae_vars,
                                   time_linking_variables=diff_vars,
                                   outlvl=idaeslog.DEBUG,
                                   solve_initial_conditions=True)

    assert degrees_of_freedom(mod.fs) == 0

    assert mod.fs.cstr.outlet.conc_mol[1, 'S'].value == approx(10.189,
                                                               abs=1e-3)
    assert mod.fs.cstr.outlet.conc_mol[1, 'C'].value == approx(0.4275,
                                                               abs=1e-4)
    assert mod.fs.cstr.outlet.conc_mol[1, 'E'].value == approx(0.0541,
                                                               abs=1e-4)
    assert mod.fs.cstr.outlet.conc_mol[1, 'P'].value == approx(0.3503,
                                                               abs=1e-4)

    initialize_by_element_in_range(mod.fs,
                                   mod.fs.time,
                                   1,
                                   2,
                                   solver=solver,
                                   dae_vars=dae_vars,
                                   outlvl=idaeslog.DEBUG)

    assert degrees_of_freedom(mod.fs) == 0
    for con in activated_equalities_generator(mod.fs):
        assert value(con.body) - value(con.upper) < 1e-5

    assert mod.fs.cstr.outlet.conc_mol[2, 'S'].value == approx(11.263,
                                                               abs=1e-3)
    assert mod.fs.cstr.outlet.conc_mol[2, 'C'].value == approx(0.4809,
                                                               abs=1e-4)
    assert mod.fs.cstr.outlet.conc_mol[2, 'E'].value == approx(0.0538,
                                                               abs=1e-4)
    assert mod.fs.cstr.outlet.conc_mol[2, 'P'].value == approx(0.4372,
                                                               abs=1e-4)
예제 #8
0
파일: model.py 프로젝트: IDAES/publications
def get_data_from_steady_model(m, time):
    assert len(time) == 1
    t0 = next(iter(time))
    scalar_vars, dae_vars = flatten_dae_components(m, time, pyo.Var)
    scalar_data = {
        str(pyo.ComponentUID(var)): var.value
        for var in scalar_vars
    }
    dae_data = {
        str(pyo.ComponentUID(var.referent)): var[t0].value
        for var in dae_vars
    }
    return scalar_data, dae_data
예제 #9
0
def reshape(m):
    time = m.time

    # Create block to hold time-decomposition
    m.time_block = pyo.Block(time)

    # Identify time-indexed components
    scalar_vars, dae_vars = flatten_dae_components(m, time, pyo.Var)
    scalar_cons, dae_cons = flatten_dae_components(m, time, pyo.Constraint)

    for t in time:
        b = m.time_block[t]
        var_list = []
        b.vars = pyo.Reference([var[t] for var in dae_vars])
        con_list = []
        for con in dae_cons:
            try:
                condata = con[t]
                con_list.append(condata)
            except KeyError:
                # For discretization equations, which are skipped at t0
                pass
        b.cons = pyo.Reference(con_list)
예제 #10
0
    def test_2dim_set(self):
        m = ConcreteModel()
        m.time = ContinuousSet(bounds=(0, 1))

        m.v = Var(m.time, [('a', 1), ('b', 2)])

        scalar, dae = flatten_dae_components(m, m.time, Var)
        self.assertEqual(len(scalar), 0)
        ref_data = {
            self._hashRef(Reference(m.v[:, 'a', 1])),
            self._hashRef(Reference(m.v[:, 'b', 2])),
        }
        self.assertEqual(len(dae), len(ref_data))
        for ref in dae:
            self.assertIn(self._hashRef(ref), ref_data)
예제 #11
0
    def test_constraint_skip(self):
        m = ConcreteModel()
        m.time = ContinuousSet(bounds=(0, 1))

        m.v = Var(m.time)

        def c_rule(m, t):
            if t == m.time.first():
                return Constraint.Skip
            return m.v[t] == 1.

        m.c = Constraint(m.time, rule=c_rule)
        scalar, dae = flatten_dae_components(m, m.time, Constraint)

        ref_data = {
            self._hashRef(Reference(m.c[:])),
        }
        self.assertEqual(len(dae), len(ref_data))
        for ref in dae:
            self.assertIn(self._hashRef(ref), ref_data)
예제 #12
0
    def test_indexed_block(self):
        m = ConcreteModel()
        m.time = ContinuousSet(bounds=(0, 1))
        m.comp = Set(initialize=['a', 'b'])

        def bb_rule(bb, t):
            bb.dae_var = Var()

        def b_rule(b, c):
            b.bb = Block(m.time, rule=bb_rule)

        m.b = Block(m.comp, rule=b_rule)

        scalar, dae = flatten_dae_components(m, m.time, Var)
        self.assertEqual(len(scalar), 0)
        ref_data = {
            self._hashRef(Reference(m.b['a'].bb[:].dae_var)),
            self._hashRef(Reference(m.b['b'].bb[:].dae_var)),
        }
        self.assertEqual(len(dae), len(ref_data))
        for ref in dae:
            self.assertIn(self._hashRef(ref), ref_data)
예제 #13
0
    def test_constraint(self):
        m = ConcreteModel()
        m.time = ContinuousSet(bounds=(0, 1))
        m.comp = Set(initialize=['a', 'b'])
        m.v0 = Var()
        m.v1 = Var(m.time)
        m.v2 = Var(m.time, m.comp)

        def c0_rule(m):
            return m.v0 == 1

        m.c0 = Constraint(rule=c0_rule)

        def c1_rule(m, t):
            return m.v1[t] == 3

        m.c1 = Constraint(m.time, rule=c1_rule)

        @m.Block(m.time)
        def b(b, t):
            def c2_rule(b, j):
                return b.model().v2[t, j] == 5

            b.c2 = Constraint(m.comp, rule=c2_rule)

        scalar, dae = flatten_dae_components(m, m.time, Constraint)
        hash_scalar = {id(s) for s in scalar}
        self.assertIn(id(m.c0), hash_scalar)

        ref_data = {
            self._hashRef(Reference(m.c1[:])),
            self._hashRef(Reference(m.b[:].c2['a'])),
            self._hashRef(Reference(m.b[:].c2['b'])),
        }
        self.assertEqual(len(dae), len(ref_data))
        for ref in dae:
            self.assertIn(self._hashRef(ref), ref_data)
예제 #14
0
    def test_2level_model(self):
        m = ConcreteModel()
        m.T = ContinuousSet(bounds=(0, 1))

        @m.Block([1, 2], m.T)
        def B(b, i, t):
            @b.Block(list(range(2 * i, 2 * i + 2)))
            def bb(bb, j):
                bb.y = Var([10, 11])

            b.x = Var(list(range(2 * i, 2 * i + 2)))

        regular, time = flatten_dae_components(m, m.T, Var)
        self.assertEqual(len(regular), 0)
        # Output for debugging
        #for v in time:
        #    v.pprint()
        #    for _ in v.values():
        #        print"     -> ", _.name
        ref_data = {
            self._hashRef(Reference(m.B[1, :].x[2])),
            self._hashRef(Reference(m.B[1, :].x[3])),
            self._hashRef(Reference(m.B[2, :].x[4])),
            self._hashRef(Reference(m.B[2, :].x[5])),
            self._hashRef(Reference(m.B[1, :].bb[2].y[10])),
            self._hashRef(Reference(m.B[1, :].bb[2].y[11])),
            self._hashRef(Reference(m.B[1, :].bb[3].y[10])),
            self._hashRef(Reference(m.B[1, :].bb[3].y[11])),
            self._hashRef(Reference(m.B[2, :].bb[4].y[10])),
            self._hashRef(Reference(m.B[2, :].bb[4].y[11])),
            self._hashRef(Reference(m.B[2, :].bb[5].y[10])),
            self._hashRef(Reference(m.B[2, :].bb[5].y[11])),
        }
        self.assertEqual(len(time), len(ref_data))
        for ref in time:
            self.assertIn(self._hashRef(ref), ref_data)
예제 #15
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)
예제 #16
0
def test_categorize_1():
    """
    This test categorizes the enzyme cstr "as intended."
    Volume is used as a measurement, and solvent flow rates are
    fixed, but otherwise equations are as expected.
    """
    model = make_model(horizon=1, ntfe=5, ntcp=2)
    time = model.fs.time

    init_input_list = [
        model.fs.mixer.S_inlet.flow_vol[0],
        model.fs.mixer.E_inlet.flow_vol[0],
    ]
    init_input_set = ComponentSet(init_input_list)

    init_deriv_list = [
        model.fs.cstr.control_volume.energy_accumulation[0, 'aq'],
        *list(model.fs.cstr.control_volume.material_accumulation[0, 'aq', :]),
    ]
    init_deriv_set = ComponentSet(init_deriv_list)

    init_diff_list = [
        model.fs.cstr.control_volume.energy_holdup[0, 'aq'],
        *list(model.fs.cstr.control_volume.material_holdup[0, 'aq', :]),
    ]
    init_diff_set = ComponentSet(init_diff_list)

    init_fixed_list = [
        model.fs.mixer.E_inlet.temperature[0],
        model.fs.mixer.S_inlet.temperature[0],
        *list(model.fs.mixer.E_inlet.conc_mol[0, :]),
        *list(model.fs.mixer.S_inlet.conc_mol[0, :]),
        model.fs.cstr.outlet.conc_mol[0, 'Solvent'],
        model.fs.mixer.outlet.conc_mol[0, 'Solvent'],
    ]
    init_fixed_set = ComponentSet(init_fixed_list)

    init_meas_list = [
        model.fs.cstr.control_volume.energy_holdup[0, 'aq'],
        model.fs.cstr.control_volume.volume[0],
        *list(model.fs.cstr.control_volume.material_holdup[0, 'aq', :]),
    ]
    init_meas_set = ComponentSet(init_meas_list)
    # Solvent holdup is not a measurement; we measure volume instead
    init_meas_set.remove(
        model.fs.cstr.control_volume.material_holdup[0, 'aq', 'Solvent'])

    init_alg_list = [
        model.fs.cstr.outlet.flow_vol[0],
        model.fs.cstr.outlet.temperature[0],
        model.fs.cstr.inlet.flow_vol[0],
        model.fs.cstr.inlet.temperature[0],
        model.fs.mixer.outlet.flow_vol[0],
        model.fs.mixer.outlet.temperature[0],
        model.fs.cstr.control_volume.volume[0],
        *list(model.fs.cstr.control_volume.properties_out[0].flow_mol_comp[:]),
        *list(model.fs.cstr.inlet.conc_mol[0, :]),
        *list(model.fs.cstr.control_volume.properties_in[0].flow_mol_comp[:]),
        *list(model.fs.cstr.control_volume.rate_reaction_generation[0,
                                                                    'aq', :]),
        *list(model.fs.mixer.mixed_state[0].flow_mol_comp[:]),
        *list(model.fs.mixer.E_inlet_state[0].flow_mol_comp[:]),
        *list(model.fs.mixer.S_inlet_state[0].flow_mol_comp[:]),
        *list(model.fs.cstr.outlet.conc_mol[0, :]),
        *list(model.fs.mixer.outlet.conc_mol[0, :]),
        *list(model.fs.cstr.control_volume.reactions[0].reaction_coef[:]),
        *list(model.fs.cstr.control_volume.reactions[0].reaction_rate[:]),
        *list(model.fs.cstr.control_volume.rate_reaction_extent[0, :]),
    ]
    init_alg_set = ComponentSet(init_alg_list)
    # solvent outlet concentrations are not algebraic variables as
    # it is fixed.
    init_alg_set.remove(model.fs.cstr.outlet.conc_mol[0, 'Solvent'])
    init_alg_set.remove(model.fs.mixer.outlet.conc_mol[0, 'Solvent'])

    scalar_vars, dae_vars = flatten_dae_components(model, time, ctype=Var)
    category_dict = categorize_dae_variables(dae_vars, time, init_input_list)

    input_vars = category_dict[VC.INPUT]
    diff_vars = category_dict[VC.DIFFERENTIAL]
    deriv_vars = category_dict[VC.DERIVATIVE]
    fixed_vars = category_dict[VC.FIXED]
    alg_vars = category_dict[VC.ALGEBRAIC]
    meas_vars = category_dict[VC.MEASUREMENT]

    assert len(input_vars) == len(init_input_set)
    for v in input_vars:
        assert v[0] in init_input_set

    assert len(deriv_vars) == len(init_deriv_set)
    for v in deriv_vars:
        assert v[0] in init_deriv_set

    assert len(diff_vars) == len(init_deriv_set)
    for v in diff_vars:
        assert v[0] in init_diff_set

    assert len(fixed_vars) == len(init_fixed_set)
    for v in fixed_vars:
        assert v[0] in init_fixed_set

    assert len(alg_vars) == len(init_alg_set)
    for v in alg_vars:
        assert v[0] in init_alg_set

    assert len(meas_vars) == len(init_meas_set)
    for v in meas_vars:
        assert v[0] in init_meas_set

    assert len(scalar_vars) == 0
예제 #17
0
def test_categorize_4():
    """
    This tests categorization when a psuedo-steady state
    approximation is used. Energy accumulation and accumulation of E
    are fixed, the corresponding initial conditions unfixed, and
    the corresponding discretization equations deactivated.
    """
    model = make_model(horizon=1, ntfe=5, ntcp=2)
    time = model.fs.time

    CV = model.fs.cstr.control_volume
    CV.energy_accumulation[:, 'aq'].fix(0.)
    CV.material_accumulation[:, 'aq', 'E'].fix(0.)
    CV.energy_holdup[0, 'aq'].unfix()
    CV.material_holdup[0, 'aq', 'E'].unfix()
    CV.energy_accumulation_disc_eq.deactivate()
    CV.material_accumulation_disc_eq.deactivate()

    init_input_list = [
        model.fs.mixer.S_inlet.flow_vol[0],
        model.fs.mixer.E_inlet.flow_vol[0],
    ]
    init_input_set = ComponentSet(init_input_list)

    init_deriv_list = [
        CV.material_accumulation[0, 'aq', 'C'],
        CV.material_accumulation[0, 'aq', 'S'],
        CV.material_accumulation[0, 'aq', 'P'],
        CV.material_accumulation[0, 'aq', 'Solvent'],
    ]
    init_deriv_set = ComponentSet(init_deriv_list)

    init_diff_list = [
        CV.material_holdup[0, 'aq', 'C'],
        CV.material_holdup[0, 'aq', 'S'],
        CV.material_holdup[0, 'aq', 'P'],
        CV.material_holdup[0, 'aq', 'Solvent'],
    ]
    init_diff_set = ComponentSet(init_diff_list)

    init_fixed_list = [
        model.fs.cstr.control_volume.energy_accumulation[0, 'aq'],
        CV.material_accumulation[0, 'aq', 'E'],
        model.fs.mixer.E_inlet.temperature[0],
        model.fs.mixer.S_inlet.temperature[0],
        *list(model.fs.mixer.E_inlet.conc_mol[0, :]),
        *list(model.fs.mixer.S_inlet.conc_mol[0, :]),
        model.fs.cstr.outlet.conc_mol[0, 'Solvent'],
        model.fs.mixer.outlet.conc_mol[0, 'Solvent'],
    ]
    init_fixed_set = ComponentSet(init_fixed_list)

    init_meas_list = [
        model.fs.cstr.control_volume.volume[0],
        CV.material_holdup[0, 'aq', 'P'],
        CV.material_holdup[0, 'aq', 'C'],
        CV.material_holdup[0, 'aq', 'S'],
    ]
    init_meas_set = ComponentSet(init_meas_list)

    init_alg_list = [
        model.fs.cstr.control_volume.energy_holdup[0, 'aq'],
        CV.material_holdup[0, 'aq', 'E'],
        model.fs.cstr.outlet.flow_vol[0],
        model.fs.cstr.outlet.temperature[0],
        model.fs.cstr.inlet.flow_vol[0],
        model.fs.cstr.inlet.temperature[0],
        model.fs.mixer.outlet.flow_vol[0],
        model.fs.mixer.outlet.temperature[0],
        model.fs.cstr.control_volume.volume[0],
        *list(model.fs.cstr.control_volume.properties_out[0].flow_mol_comp[:]),
        *list(model.fs.cstr.inlet.conc_mol[0, :]),
        *list(model.fs.cstr.control_volume.properties_in[0].flow_mol_comp[:]),
        *list(model.fs.cstr.control_volume.rate_reaction_generation[0,
                                                                    'aq', :]),
        *list(model.fs.mixer.mixed_state[0].flow_mol_comp[:]),
        *list(model.fs.mixer.E_inlet_state[0].flow_mol_comp[:]),
        *list(model.fs.mixer.S_inlet_state[0].flow_mol_comp[:]),
        *list(model.fs.cstr.outlet.conc_mol[0, :]),
        *list(model.fs.mixer.outlet.conc_mol[0, :]),
        *list(model.fs.cstr.control_volume.reactions[0].reaction_coef[:]),
        *list(model.fs.cstr.control_volume.reactions[0].reaction_rate[:]),
        *list(model.fs.cstr.control_volume.rate_reaction_extent[0, :]),
    ]
    init_alg_set = ComponentSet(init_alg_list)
    # solvent outlet concentrations are not algebraic variables as
    # it is fixed.
    init_alg_set.remove(model.fs.cstr.outlet.conc_mol[0, 'Solvent'])
    init_alg_set.remove(model.fs.mixer.outlet.conc_mol[0, 'Solvent'])

    scalar_vars, dae_vars = flatten_dae_components(model, time, ctype=Var)
    category_dict = categorize_dae_variables(dae_vars, time, init_input_list)

    input_vars = category_dict[VC.INPUT]
    diff_vars = category_dict[VC.DIFFERENTIAL]
    deriv_vars = category_dict[VC.DERIVATIVE]
    fixed_vars = category_dict[VC.FIXED]
    alg_vars = category_dict[VC.ALGEBRAIC]
    meas_vars = category_dict[VC.MEASUREMENT]

    assert len(input_vars) == len(init_input_set)
    for v in input_vars:
        assert v[0] in init_input_set

    assert len(deriv_vars) == len(init_deriv_set)
    for v in deriv_vars:
        assert v[0] in init_deriv_set

    assert len(diff_vars) == len(init_deriv_set)
    for v in diff_vars:
        assert v[0] in init_diff_set

    assert len(fixed_vars) == len(init_fixed_set)
    for v in fixed_vars:
        assert v[0] in init_fixed_set

    assert len(alg_vars) == len(init_alg_set)
    for v in alg_vars:
        assert v[0] in init_alg_set

    assert len(meas_vars) == len(init_meas_set)
    for v in meas_vars:
        assert v[0] in init_meas_set

    assert len(scalar_vars) == 0
예제 #18
0
def test_categorize_2():
    """
    In this instance, temperature is "measured" (used as an initial condition)
    instead of energy_holdup, conc[P] is measured instead of holdup[P],
    and accumulation[C] is measured instead of holdup[C].
    """
    model = make_model(horizon=1, ntfe=5, ntcp=2)
    time = model.fs.time
    t0 = time.first()

    material_holdup = model.fs.cstr.control_volume.material_holdup
    material_accumulation = model.fs.cstr.control_volume.material_accumulation
    energy_holdup = model.fs.cstr.control_volume.energy_holdup
    energy_accumulation = model.fs.cstr.control_volume.energy_accumulation
    conc_mol = model.fs.cstr.outlet.conc_mol

    # Specify temperature instead of energy holdup
    energy_holdup[t0, 'aq'].unfix()
    model.fs.cstr.outlet.temperature[t0].fix(300)

    # Specify C_P instead of holdup
    material_holdup[t0, 'aq', 'P'].unfix()
    conc_mol[t0, 'P'].fix(0.)

    # Specify accumulation of C instead of holdup
    # You might want to do this if you want to start at steady state,
    # but don't know the value of every variable at the steady state
    # you want to start at...
    material_holdup[t0, 'aq', 'C'].unfix()
    material_accumulation[t0, 'aq', 'C'].fix(0.)

    init_input_list = [
        model.fs.mixer.S_inlet.flow_vol[0],
        model.fs.mixer.E_inlet.flow_vol[0],
    ]
    init_input_set = ComponentSet(init_input_list)

    init_deriv_list = [
        model.fs.cstr.control_volume.energy_accumulation[0, 'aq'],
        *list(model.fs.cstr.control_volume.material_accumulation[0, 'aq', :]),
    ]
    init_deriv_set = ComponentSet(init_deriv_list)

    init_diff_list = [
        model.fs.cstr.control_volume.energy_holdup[0, 'aq'],
        *list(model.fs.cstr.control_volume.material_holdup[0, 'aq', :]),
    ]
    init_diff_set = ComponentSet(init_diff_list)

    init_fixed_list = [
        model.fs.mixer.E_inlet.temperature[0],
        model.fs.mixer.S_inlet.temperature[0],
        *list(model.fs.mixer.E_inlet.conc_mol[0, :]),
        *list(model.fs.mixer.S_inlet.conc_mol[0, :]),
        model.fs.cstr.outlet.conc_mol[0, 'Solvent'],
        model.fs.mixer.outlet.conc_mol[0, 'Solvent'],
    ]
    init_fixed_set = ComponentSet(init_fixed_list)

    init_meas_list = [
        model.fs.cstr.outlet.temperature[0],
        model.fs.cstr.control_volume.volume[0],
        model.fs.cstr.control_volume.material_holdup[0, 'aq', 'S'],
        model.fs.cstr.control_volume.material_holdup[0, 'aq', 'E'],
        model.fs.cstr.control_volume.material_holdup[0, 'aq', 'S'],
        model.fs.cstr.control_volume.material_accumulation[0, 'aq', 'C'],
        model.fs.cstr.outlet.conc_mol[0, 'P'],
    ]
    init_meas_set = ComponentSet(init_meas_list)
    # No need to remove solvent holdup here as it was not added to this list.

    init_alg_list = [
        model.fs.cstr.outlet.flow_vol[0],
        model.fs.cstr.outlet.temperature[0],
        model.fs.cstr.inlet.flow_vol[0],
        model.fs.cstr.inlet.temperature[0],
        model.fs.mixer.outlet.flow_vol[0],
        model.fs.mixer.outlet.temperature[0],
        model.fs.cstr.control_volume.volume[0],
        *list(model.fs.cstr.control_volume.properties_out[0].flow_mol_comp[:]),
        *list(model.fs.cstr.inlet.conc_mol[0, :]),
        *list(model.fs.cstr.control_volume.properties_in[0].flow_mol_comp[:]),
        *list(model.fs.cstr.control_volume.rate_reaction_generation[0,
                                                                    'aq', :]),
        *list(model.fs.mixer.mixed_state[0].flow_mol_comp[:]),
        *list(model.fs.mixer.E_inlet_state[0].flow_mol_comp[:]),
        *list(model.fs.mixer.S_inlet_state[0].flow_mol_comp[:]),
        *list(model.fs.cstr.outlet.conc_mol[0, :]),
        *list(model.fs.mixer.outlet.conc_mol[0, :]),
        *list(model.fs.cstr.control_volume.reactions[0].reaction_coef[:]),
        *list(model.fs.cstr.control_volume.reactions[0].reaction_rate[:]),
        *list(model.fs.cstr.control_volume.rate_reaction_extent[0, :]),
    ]
    init_alg_set = ComponentSet(init_alg_list)
    # solvent outlet concentrations are not algebraic variables as
    # it is fixed.
    init_alg_set.remove(model.fs.cstr.outlet.conc_mol[0, 'Solvent'])
    init_alg_set.remove(model.fs.mixer.outlet.conc_mol[0, 'Solvent'])

    scalar_vars, dae_vars = flatten_dae_components(model, time, ctype=Var)
    category_dict = categorize_dae_variables(dae_vars, time, init_input_list)

    input_vars = category_dict[VC.INPUT]
    diff_vars = category_dict[VC.DIFFERENTIAL]
    deriv_vars = category_dict[VC.DERIVATIVE]
    fixed_vars = category_dict[VC.FIXED]
    alg_vars = category_dict[VC.ALGEBRAIC]
    meas_vars = category_dict[VC.MEASUREMENT]

    assert len(input_vars) == len(init_input_set)
    for v in input_vars:
        assert v[0] in init_input_set

    assert len(deriv_vars) == len(init_deriv_set)
    for v in deriv_vars:
        assert v[0] in init_deriv_set

    assert len(diff_vars) == len(init_deriv_set)
    for v in diff_vars:
        assert v[0] in init_diff_set

    assert len(fixed_vars) == len(init_fixed_set)
    for v in fixed_vars:
        assert v[0] in init_fixed_set

    assert len(alg_vars) == len(init_alg_set)
    for v in alg_vars:
        assert v[0] in init_alg_set

    assert len(meas_vars) == len(init_meas_set)
    for v in meas_vars:
        assert v[0] in init_meas_set

    assert len(scalar_vars) == 0
예제 #19
0
def test_categorize_3():
    """
    In this test we fix one of the differential variables.
    This is the case where somebody wants to run an isothermal
    CSTR.
    """
    model = make_model(horizon=1, ntfe=5, ntcp=2)
    time = model.fs.time

    CV = model.fs.cstr.control_volume
    CV.energy_holdup.fix(300)
    CV.energy_accumulation_disc_eq.deactivate()

    init_input_list = [
        model.fs.mixer.S_inlet.flow_vol[0],
        model.fs.mixer.E_inlet.flow_vol[0],
    ]
    init_input_set = ComponentSet(init_input_list)

    init_deriv_list = [
        *list(model.fs.cstr.control_volume.material_accumulation[0, 'aq', :]),
    ]
    init_deriv_set = ComponentSet(init_deriv_list)

    init_diff_list = [
        *list(model.fs.cstr.control_volume.material_holdup[0, 'aq', :]),
    ]
    init_diff_set = ComponentSet(init_diff_list)

    init_fixed_list = [
        # Energy holdup has been fixed
        model.fs.cstr.control_volume.energy_holdup[0, 'aq'],
        model.fs.mixer.E_inlet.temperature[0],
        model.fs.mixer.S_inlet.temperature[0],
        *list(model.fs.mixer.E_inlet.conc_mol[0, :]),
        *list(model.fs.mixer.S_inlet.conc_mol[0, :]),
        model.fs.cstr.outlet.conc_mol[0, 'Solvent'],
        model.fs.mixer.outlet.conc_mol[0, 'Solvent'],
    ]
    init_fixed_set = ComponentSet(init_fixed_list)

    init_meas_list = [
        model.fs.cstr.control_volume.volume[0],
        *list(model.fs.cstr.control_volume.material_holdup[0, 'aq', :]),
    ]
    init_meas_set = ComponentSet(init_meas_list)
    # Solvent holdup is not a measurement; we measure volume instead
    init_meas_set.remove(
        model.fs.cstr.control_volume.material_holdup[0, 'aq', 'Solvent'])

    init_alg_list = [
        # Since energy_holdup is fixed, energy_accumulation is not
        # considered a derivative. Instead it is algebraic.
        model.fs.cstr.control_volume.energy_accumulation[0, 'aq'],
        model.fs.cstr.outlet.flow_vol[0],
        model.fs.cstr.outlet.temperature[0],
        model.fs.cstr.inlet.flow_vol[0],
        model.fs.cstr.inlet.temperature[0],
        model.fs.mixer.outlet.flow_vol[0],
        model.fs.mixer.outlet.temperature[0],
        model.fs.cstr.control_volume.volume[0],
        *list(model.fs.cstr.control_volume.properties_out[0].flow_mol_comp[:]),
        *list(model.fs.cstr.inlet.conc_mol[0, :]),
        *list(model.fs.cstr.control_volume.properties_in[0].flow_mol_comp[:]),
        *list(model.fs.cstr.control_volume.rate_reaction_generation[0,
                                                                    'aq', :]),
        *list(model.fs.mixer.mixed_state[0].flow_mol_comp[:]),
        *list(model.fs.mixer.E_inlet_state[0].flow_mol_comp[:]),
        *list(model.fs.mixer.S_inlet_state[0].flow_mol_comp[:]),
        *list(model.fs.cstr.outlet.conc_mol[0, :]),
        *list(model.fs.mixer.outlet.conc_mol[0, :]),
        *list(model.fs.cstr.control_volume.reactions[0].reaction_coef[:]),
        *list(model.fs.cstr.control_volume.reactions[0].reaction_rate[:]),
        *list(model.fs.cstr.control_volume.rate_reaction_extent[0, :]),
    ]
    init_alg_set = ComponentSet(init_alg_list)
    # solvent outlet concentrations are not algebraic variables as
    # it is fixed.
    init_alg_set.remove(model.fs.cstr.outlet.conc_mol[0, 'Solvent'])
    init_alg_set.remove(model.fs.mixer.outlet.conc_mol[0, 'Solvent'])

    scalar_vars, dae_vars = flatten_dae_components(model, time, ctype=Var)
    category_dict = categorize_dae_variables(dae_vars, time, init_input_list)

    input_vars = category_dict[VC.INPUT]
    diff_vars = category_dict[VC.DIFFERENTIAL]
    deriv_vars = category_dict[VC.DERIVATIVE]
    fixed_vars = category_dict[VC.FIXED]
    alg_vars = category_dict[VC.ALGEBRAIC]
    meas_vars = category_dict[VC.MEASUREMENT]

    assert len(input_vars) == len(init_input_set)
    for v in input_vars:
        assert v[0] in init_input_set

    assert len(deriv_vars) == len(init_deriv_set)
    for v in deriv_vars:
        assert v[0] in init_deriv_set

    assert len(diff_vars) == len(init_deriv_set)
    for v in diff_vars:
        assert v[0] in init_diff_set

    assert len(fixed_vars) == len(init_fixed_set)
    for v in fixed_vars:
        assert v[0] in init_fixed_set

    assert len(alg_vars) == len(init_alg_set)
    for v in alg_vars:
        assert v[0] in init_alg_set

    assert len(meas_vars) == len(init_meas_set)
    for v in meas_vars:
        assert v[0] in init_meas_set

    assert len(scalar_vars) == 0
예제 #20
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()
예제 #21
0
def main(plot_switch=False):

    # This tests the same model constructed in the test_nmpc_constructor_1 file
    m_controller = make_model(horizon=3, ntfe=30, ntcp=2, bounds=True)
    sample_time = 0.5
    m_plant = make_model(horizon=sample_time, ntfe=5, ntcp=2)
    time_plant = m_plant.fs.time

    solve_consistent_initial_conditions(m_plant, time_plant, solver)

    #####
    # Flatten and categorize controller model
    #####
    model = m_controller
    time = model.fs.time
    t0 = time.first()
    t1 = time[2]
    scalar_vars, dae_vars = flatten_dae_components(
        model,
        time,
        pyo.Var,
    )
    scalar_cons, dae_cons = flatten_dae_components(
        model,
        time,
        pyo.Constraint,
    )
    inputs = [
        model.fs.mixer.S_inlet.flow_vol,
        model.fs.mixer.E_inlet.flow_vol,
    ]
    measurements = [
        pyo.Reference(model.fs.cstr.outlet.conc_mol[:, 'C']),
        pyo.Reference(model.fs.cstr.outlet.conc_mol[:, 'E']),
        pyo.Reference(model.fs.cstr.outlet.conc_mol[:, 'S']),
        pyo.Reference(model.fs.cstr.outlet.conc_mol[:, 'P']),
        model.fs.cstr.outlet.temperature,
    ]
    model.fs.cstr.control_volume.material_holdup[:, 'aq', 'Solvent'].fix()
    model.fs.cstr.total_flow_balance.deactivate()
    var_partition, con_partition = categorize_dae_variables_and_constraints(
        model,
        dae_vars,
        dae_cons,
        time,
        input_vars=inputs,
    )
    controller = ControllerBlock(
        model=model,
        time=time,
        measurements=measurements,
        category_dict={None: var_partition},
    )
    controller.construct()

    solve_consistent_initial_conditions(m_controller, time, solver)
    controller.initialize_to_initial_conditions()

    m_controller._dummy_obj = pyo.Objective(expr=0)
    nlp = PyomoNLP(m_controller)
    igraph = IncidenceGraphInterface(nlp)
    m_controller.del_component(m_controller._dummy_obj)
    diff_vars = [var[t1] for var in var_partition[VC.DIFFERENTIAL]]
    alg_vars = [var[t1] for var in var_partition[VC.ALGEBRAIC]]
    deriv_vars = [var[t1] for var in var_partition[VC.DERIVATIVE]]
    diff_eqns = [con[t1] for con in con_partition[CC.DIFFERENTIAL]]
    alg_eqns = [con[t1] for con in con_partition[CC.ALGEBRAIC]]

    # Assemble and factorize "derivative Jacobian"
    dfdz = nlp.extract_submatrix_jacobian(diff_vars, diff_eqns)
    dfdy = nlp.extract_submatrix_jacobian(alg_vars, diff_eqns)
    dgdz = nlp.extract_submatrix_jacobian(diff_vars, alg_eqns)
    dgdy = nlp.extract_submatrix_jacobian(alg_vars, alg_eqns)
    dfdzdot = nlp.extract_submatrix_jacobian(deriv_vars, diff_eqns)
    fact = sps.linalg.splu(dgdy.tocsc())
    dydz = fact.solve(dgdz.toarray())
    deriv_jac = dfdz - dfdy.dot(dydz)
    fact = sps.linalg.splu(dfdzdot.tocsc())
    dzdotdz = -fact.solve(deriv_jac)

    # Use some heuristic on the eigenvalues of the derivative Jacobian
    # to identify fast states.
    w, V = np.linalg.eig(dzdotdz)
    w_max = np.max(np.abs(w))
    fast_modes, = np.where(np.abs(w) > w_max / 2)
    fast_states = []
    for idx in fast_modes:
        evec = V[:, idx]
        _fast_states, _ = np.where(np.abs(evec) > 0.5)
        fast_states.extend(_fast_states)
    fast_states = set(fast_states)

    # Store components necessary for model reduction in a model-
    # independent form.
    fast_state_derivs = [
        pyo.ComponentUID(var_partition[VC.DERIVATIVE][idx].referent,
                         context=model) for idx in fast_states
    ]
    fast_state_diffs = [
        pyo.ComponentUID(var_partition[VC.DIFFERENTIAL][idx].referent,
                         context=model) for idx in fast_states
    ]
    fast_state_discs = [
        pyo.ComponentUID(con_partition[CC.DISCRETIZATION][idx].referent,
                         context=model) for idx in fast_states
    ]

    # Perform pseudo-steady state model reduction on the fast states
    # and re-categorize
    for cuid in fast_state_derivs:
        var = cuid.find_component_on(m_controller)
        var.fix(0.0)
    for cuid in fast_state_diffs:
        var = cuid.find_component_on(m_controller)
        var[t0].unfix()
    for cuid in fast_state_discs:
        con = cuid.find_component_on(m_controller)
        con.deactivate()

    var_partition, con_partition = categorize_dae_variables_and_constraints(
        model,
        dae_vars,
        dae_cons,
        time,
        input_vars=inputs,
    )
    controller.del_component(model)

    # Re-construct controller block with new categorization
    measurements = [
        pyo.Reference(model.fs.cstr.outlet.conc_mol[:, 'C']),
        pyo.Reference(model.fs.cstr.outlet.conc_mol[:, 'E']),
        pyo.Reference(model.fs.cstr.outlet.conc_mol[:, 'S']),
        pyo.Reference(model.fs.cstr.outlet.conc_mol[:, 'P']),
    ]
    controller = ControllerBlock(
        model=model,
        time=time,
        measurements=measurements,
        category_dict={None: var_partition},
    )
    controller.construct()

    #####
    # Construct dynamic block for plant
    #####
    model = m_plant
    time = model.fs.time
    t0 = time.first()
    t1 = time[2]
    scalar_vars, dae_vars = flatten_dae_components(
        model,
        time,
        pyo.Var,
    )
    scalar_cons, dae_cons = flatten_dae_components(
        model,
        time,
        pyo.Constraint,
    )
    inputs = [
        model.fs.mixer.S_inlet.flow_vol,
        model.fs.mixer.E_inlet.flow_vol,
    ]
    measurements = [
        pyo.Reference(model.fs.cstr.outlet.conc_mol[:, 'C']),
        pyo.Reference(model.fs.cstr.outlet.conc_mol[:, 'E']),
        pyo.Reference(model.fs.cstr.outlet.conc_mol[:, 'S']),
        pyo.Reference(model.fs.cstr.outlet.conc_mol[:, 'P']),
    ]
    model.fs.cstr.control_volume.material_holdup[:, 'aq', 'Solvent'].fix()
    model.fs.cstr.total_flow_balance.deactivate()

    var_partition, con_partition = categorize_dae_variables_and_constraints(
        model,
        dae_vars,
        dae_cons,
        time,
        input_vars=inputs,
    )
    plant = DynamicBlock(
        model=model,
        time=time,
        measurements=measurements,
        category_dict={None: var_partition},
    )
    plant.construct()

    p_t0 = plant.time.first()
    c_t0 = controller.time.first()
    p_ts = plant.sample_points[1]
    c_ts = controller.sample_points[1]

    controller.set_sample_time(sample_time)
    plant.set_sample_time(sample_time)

    # We now perform the "RTO" calculation: Find the optimal steady state
    # to achieve the following setpoint
    setpoint = [
        (controller.mod.fs.cstr.outlet.conc_mol[0, 'P'], 0.4),
        #(controller.mod.fs.cstr.outlet.conc_mol[0, 'S'], 0.01),
        (controller.mod.fs.cstr.outlet.conc_mol[0, 'S'], 0.1),
        (controller.mod.fs.cstr.control_volume.energy_holdup[0, 'aq'], 300),
        (controller.mod.fs.mixer.E_inlet.flow_vol[0], 0.1),
        (controller.mod.fs.mixer.S_inlet.flow_vol[0], 2.0),
        (controller.mod.fs.cstr.volume[0], 1.0),
    ]
    setpoint_weights = [
        (controller.mod.fs.cstr.outlet.conc_mol[0, 'P'], 1.),
        (controller.mod.fs.cstr.outlet.conc_mol[0, 'S'], 1.),
        (controller.mod.fs.cstr.control_volume.energy_holdup[0, 'aq'], 1.),
        (controller.mod.fs.mixer.E_inlet.flow_vol[0], 1.),
        (controller.mod.fs.mixer.S_inlet.flow_vol[0], 1.),
        (controller.mod.fs.cstr.volume[0], 1.),
    ]

    # Some of the "differential variables" that have been fixed in the
    # model file are different from the measurements listed above. We
    # unfix them here so the RTO solve is not overconstrained.
    # (The RTO solve will only automatically unfix inputs and measurements.)
    controller.mod.fs.cstr.control_volume.material_holdup[0, ...].unfix()
    controller.mod.fs.cstr.control_volume.energy_holdup[0, ...].unfix()
    #controller.mod.fs.cstr.volume[0].unfix()
    controller.mod.fs.cstr.control_volume.material_holdup[0, 'aq',
                                                          'Solvent'].fix()

    controller.add_setpoint_objective(setpoint, setpoint_weights)
    controller.solve_setpoint(solver)

    # Now we are ready to construct the tracking NMPC problem
    tracking_weights = [
        *((v, 1.) for v in controller.vectors.differential[:, 0]),
        *((v, 1.) for v in controller.vectors.input[:, 0]),
    ]

    controller.add_tracking_objective(tracking_weights)

    controller.constrain_control_inputs_piecewise_constant()

    controller.initialize_to_initial_conditions()

    # Solve the first control problem
    controller.vectors.input[...].unfix()
    controller.vectors.input[:, 0].fix()
    solver.solve(controller, tee=True)

    # For a proper NMPC simulation, we must have noise.
    # We do this by treating inputs and measurements as Gaussian random
    # variables with the following variances (and bounds).
    cstr = controller.mod.fs.cstr
    variance = [
        (cstr.outlet.conc_mol[0.0, 'S'], 0.01),
        (cstr.outlet.conc_mol[0.0, 'E'], 0.005),
        (cstr.outlet.conc_mol[0.0, 'C'], 0.01),
        (cstr.outlet.conc_mol[0.0, 'P'], 0.005),
        (cstr.outlet.temperature[0.0], 1.),
        (cstr.volume[0.0], 0.05),
    ]
    controller.set_variance(variance)
    measurement_variance = [
        v.variance for v in controller.MEASUREMENT_BLOCK[:].var
    ]
    measurement_noise_bounds = [(0.0, var[c_t0].ub)
                                for var in controller.MEASUREMENT_BLOCK[:].var]

    mx = plant.mod.fs.mixer
    variance = [
        (mx.S_inlet_state[0.0].flow_vol, 0.02),
        (mx.E_inlet_state[0.0].flow_vol, 0.001),
    ]
    plant.set_variance(variance)
    input_variance = [v.variance for v in plant.INPUT_BLOCK[:].var]
    input_noise_bounds = [(0.0, var[p_t0].ub)
                          for var in plant.INPUT_BLOCK[:].var]

    random.seed(100)

    # Extract inputs from controller and inject them into plant
    inputs = controller.generate_inputs_at_time(c_ts)
    plant.inject_inputs(inputs)

    # This "initialization" really simulates the plant with the new inputs.
    plant.vectors.input[:, :].fix()
    plant.initialize_by_solving_elements(solver)
    plant.vectors.input[:, :].fix()
    solver.solve(plant, tee=True)

    for i in range(1, 11):
        print('\nENTERING NMPC LOOP ITERATION %s\n' % i)
        measured = plant.generate_measurements_at_time(p_ts)
        plant.advance_one_sample()
        plant.initialize_to_initial_conditions()
        measured = apply_noise_with_bounds(
            measured,
            measurement_variance,
            random.gauss,
            measurement_noise_bounds,
        )

        controller.advance_one_sample()
        controller.load_measurements(measured)

        solver.solve(controller, tee=True)

        inputs = controller.generate_inputs_at_time(c_ts)
        inputs = apply_noise_with_bounds(
            inputs,
            input_variance,
            random.gauss,
            input_noise_bounds,
        )
        plant.inject_inputs(inputs)

        plant.initialize_by_solving_elements(solver)
        solver.solve(plant)

    import pdb
    pdb.set_trace()
예제 #22
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
예제 #23
0
def _test_add_noise_at_time():
    mod = make_model(horizon=2, ntfe=20)
    time = mod.fs.time
    t0 = time.first()
    assert degrees_of_freedom(mod) == 0

    scalar_vars, dae_vars = flatten_dae_components(mod.fs, time, Var)
    diff_vars = [
        Reference(mod.fs.cstr.control_volume.energy_holdup[:, 'aq']),
        Reference(mod.fs.cstr.control_volume.material_holdup[:, 'aq', 'S']),
        Reference(mod.fs.cstr.control_volume.material_holdup[:, 'aq', 'E']),
        Reference(mod.fs.cstr.control_volume.material_holdup[:, 'aq', 'C']),
        Reference(mod.fs.cstr.control_volume.material_holdup[:, 'aq', 'P'])
    ]

    for t in time:
        diff_vars[0][t].setlb(290)
        diff_vars[0][t].setub(310)
        for i in range(1, 5):
            diff_vars[i][t].setlb(0)
            diff_vars[i][t].setub(1)
            # Pretend this is mole fraction...

    assert diff_vars[0][0].value == 300
    for i in range(1, 5):
        assert diff_vars[i][0].value == 0.001

    copy_values_at_time(diff_vars, diff_vars, [t for t in time if t != t0], t0)

    for seed in [4, 8, 15, 16, 23, 42]:
        random.seed(seed)
        weights = [10, 0.001, 0.001, 0.001, 0.001]
        nom_vals = add_noise_at_time(diff_vars, 0, weights=weights)

        assert nom_vals[0][0] == 300
        assert diff_vars[0][0].value != 300
        assert diff_vars[0][0].value == approx(300, abs=2)
        for i in range(1, 5):
            assert nom_vals[0][i] == 0.001
            # ^ nom_vals indexed by time, then var-index. This is confusing,
            # might need to change (or only accept one time point at a time)
            assert diff_vars[i][0].value != 0.001
            assert diff_vars[i][0].value == approx(0.001, abs=2e-4)
            # Within four standard deviations should be a safe check

        for i in range(0, 5):
            diff_vars[i][0].set_value(nom_vals[0][i])
        # Reset and try again with new seed

    # Try providing function for uniform random
    rand_fcn = random.uniform

    random_arg_dict = {
        'range_list': [(295, 305), (0.001, 0.01), (0.001, 0.01), (0.001, 0.01),
                       (0.001, 0.01)]
    }

    def args_fcn(i, val, **kwargs):
        # args_fcn expects arguments like this
        range_list = kwargs.pop('range_list', None)
        return range_list[i]

    nom_vals = add_noise_at_time(diff_vars,
                                 0.5,
                                 random_function=rand_fcn,
                                 args_function=args_fcn,
                                 random_arg_dict=random_arg_dict)

    assert nom_vals[0.5][0] == 300
    assert diff_vars[0][0.5].value != 300
    assert 295 <= diff_vars[0][0.5].value <= 305
    for i in range(1, 5):
        assert nom_vals[0.5][i] == 0.001
        assert diff_vars[i][0.5].value != 0.001
        assert 0.001 <= diff_vars[i][0.5].value <= 0.01

    # Try to get some bound violations
    random_arg_dict = {
        'range_list': [(295, 305), (1, 2), (1, 2), (1, 2), (1, 2)]
    }

    nom_vals = add_noise_at_time(diff_vars,
                                 1,
                                 random_function=rand_fcn,
                                 args_function=args_fcn,
                                 random_arg_dict=random_arg_dict,
                                 bound_strategy='push',
                                 bound_push=0.01)

    for i in range(1, 5):
        assert diff_vars[i][1].value == 0.99

    random.seed(123)
    with pytest.raises(ValueError) as exc_test:
        # Large weights - one of these lower bounds should fail...
        nom_vals = add_noise_at_time(diff_vars,
                                     1.5,
                                     bound_strategy='discard',
                                     discard_limit=0,
                                     weights=[1, 1, 1, 1, 1],
                                     sig_0=0.05)
예제 #24
0
파일: model.py 프로젝트: IDAES/publications
def make_square_dynamic_model(**kwargs):
    steady = kwargs.pop("steady", False)

    horizon = kwargs.pop('horizon', 300.)
    tfe_width = kwargs.pop('tfe_width', 15.)
    ntcp = kwargs.pop('ntcp', 1)

    nxfe = kwargs.pop('nxfe', 10)
    nxcp = kwargs.pop('nxcp', 3)

    TIMER.start("make model")
    m = make_model(
        steady=steady,
        horizon=horizon,
        tfe_width=tfe_width,
        ntcp=ntcp,
        nxfe=nxfe,
        nxcp=nxcp,
        **kwargs,
    )
    TIMER.stop("make model")
    time = m.fs.time
    t0 = time.first()

    design_vars = ["fs.MB.bed_diameter", "fs.MB.bed_height"]
    design_var_values = {var: None for var in design_vars}
    dynamic_inputs = [
        "fs.MB.gas_inlet.flow_mol[*]",
        "fs.MB.gas_inlet.pressure[*]",
        "fs.MB.gas_inlet.temperature[*]",
        "fs.MB.gas_inlet.mole_frac_comp[*,CO2]",
        "fs.MB.gas_inlet.mole_frac_comp[*,H2O]",
        "fs.MB.gas_inlet.mole_frac_comp[*,CH4]",
        "fs.MB.solid_inlet.flow_mass[*]",
        "fs.MB.solid_inlet.temperature[*]",
        "fs.MB.solid_inlet.particle_porosity[*]",
        "fs.MB.solid_inlet.mass_frac_comp[*,Fe2O3]",
        "fs.MB.solid_inlet.mass_frac_comp[*,Fe3O4]",
        "fs.MB.solid_inlet.mass_frac_comp[*,Al2O3]",
    ]
    input_values = {var: None for var in dynamic_inputs}
    fix_design_variables(m, design_var_values)
    fix_dynamic_inputs(m, time, input_values)

    flatten_out = kwargs.pop("flatten_out", None)

    # Identify proper differential variables to fix and fix them
    # at t0
    input_vars = [m.find_component(name) for name in dynamic_inputs]
    TIMER.start("flatten vars")
    scalar_vars, dae_vars = flatten_dae_components(m, time, pyo.Var)
    if flatten_out is not None:
        flatten_out[0] = scalar_vars
        flatten_out[1] = dae_vars
    TIMER.stop("flatten vars")
    TIMER.start("flatten cons")
    scalar_cons, dae_cons = flatten_dae_components(m, time, pyo.Constraint)
    TIMER.stop("flatten cons")
    TIMER.start("categorize")
    var_cat, con_cat = categorize_dae_variables_and_constraints(
        m, dae_vars, dae_cons, time, input_vars=input_vars)
    TIMER.stop("categorize")
    VC = VariableCategory
    CC = ConstraintCategory
    diff_vars = var_cat[VC.DIFFERENTIAL]
    for var in diff_vars:
        var[t0].fix()
    return m, var_cat, con_cat
예제 #25
0
def initialize_by_element_in_range(model,
                                   time,
                                   t_start,
                                   t_end,
                                   time_linking_vars=[],
                                   dae_vars=[],
                                   max_linking_range=0,
                                   **kwargs):
    """Function for solving a square model, time element-by-time element,
    between specified start and end times.

    Args:
        model : Flowsheet model to solve
        t_start : Beginning of timespan over which to solve
        t_end : End of timespan over which to solve

    Kwargs:
        solver : Solver option used to solve portions of the square model
        outlvl : idaes.logger output level
    """
    solver = kwargs.pop('solver', SolverFactory('ipopt'))
    outlvl = kwargs.pop('outlvl', idaeslog.NOTSET)
    init_log = idaeslog.getInitLogger('nmpc', outlvl)
    solver_log = idaeslog.getSolveLogger('nmpc', outlvl)
    solve_initial_conditions = kwargs.pop('solve_initial_conditions', False)

    #TODO: Move to docstring
    # Variables that will be fixed for time points outside the finite element
    # when constraints for a finite element are activated.
    # For a "normal" process, these should just be differential variables
    # (and maybe derivative variables). For a process with a (PID) controller,
    # these should also include variables used by the controller.
    # If these variables are not specified,

    # Timespan over which these variables will be fixed, counting backwards
    # from the first time point in the finite element (which will always be
    # fixed)
    # Should I specify max_linking_range as an integer number of finite
    # elements, an integer number of time points, or a float in actual time
    # units? Go with latter for now.

    assert t_start in time.get_finite_elements()
    assert t_end in time.get_finite_elements()
    #assert degrees_of_freedom(model) == 0
    # No need to check dof here as we will check right before each solve

    #dae_vars = kwargs.pop('dae_vars', [])
    if not dae_vars:
        scalar_vars, dae_vars = flatten_dae_components(model, time, ctype=Var)
        for var in scalar_vars:
            var.fix()
        deactivate_constraints_unindexed_by(model, time)

    ncp = time.get_discretization_info()['ncp']

    fe_in_range = [
        i for i, fe in enumerate(time.get_finite_elements())
        if fe >= t_start and fe <= t_end
    ]
    t_in_range = [t for t in time if t >= t_start and t <= t_end]

    fe_in_range.pop(0)
    n_fe_in_range = len(fe_in_range)

    was_originally_active = get_activity_dict(model)
    was_originally_fixed = get_fixed_dict(model)

    # Deactivate model
    if not solve_initial_conditions:
        time_list = [t for t in time]
        deactivated = deactivate_model_at(model,
                                          time,
                                          time_list,
                                          outlvl=idaeslog.ERROR)
    else:
        time_list = [t for t in time if t != time.first()]
        deactivated = deactivate_model_at(model,
                                          time,
                                          time_list,
                                          outlvl=idaeslog.ERROR)

        assert degrees_of_freedom(model) == 0
        with idaeslog.solver_log(solver_log, level=idaeslog.DEBUG) as slc:
            results = solver.solve(model, tee=slc.tee)
        if results.solver.termination_condition == TerminationCondition.optimal:
            pass
        else:
            raise ValueError(
                'Failed to solve for consistent initial conditions.')

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

    # "Integration" loop
    for i in fe_in_range:
        t_prev = time[(i - 1) * ncp + 1]

        fe = [time[k] for k in range((i - 1) * ncp + 2, i * ncp + 2)]

        con_list = []
        for t in fe:
            # These will be fixed vars in constraints at t
            # Probably not necessary to record at what t
            # they occur
            for comp in deactivated[t]:
                if was_originally_active[id(comp)]:
                    comp.activate()
                    if not time_linking_vars:
                        if isinstance(comp, _ConstraintData):
                            con_list.append(comp)
                        elif isinstance(comp, _BlockData):
                            # Active here should be independent of whether block
                            # was active
                            con_list.extend(
                                list(
                                    comp.component_data_objects(Constraint,
                                                                active=True)))

        if not time_linking_vars:
            fixed_vars = []
            for con in con_list:
                for var in identify_variables(con.expr, include_fixed=False):
                    # use var_locator/ComponentMap to get index somehow
                    t_idx = get_implicit_index_of_set(var, time)
                    if t_idx is None:
                        assert not is_in_block_indexed_by(var, time)
                        continue
                    if t_idx <= t_prev:
                        fixed_vars.append(var)
                        var.fix()
        else:
            fixed_vars = []
            time_range = [
                t for t in time
                if t_prev - t <= max_linking_range and t <= t_prev
            ]
            time_range = [t_prev]
            for _slice in time_linking_vars:
                for t in time_range:
                    #if not _slice[t].fixed:
                    _slice[t].fix()
                    fixed_vars.append(_slice[t])

        # Here I assume that the only variables that can appear in
        # constraints at a different (later) time index are derivatives
        # and differential variables (they do so in the discretization
        # equations) and that they only participate at t_prev.
        #
        # This is not the case for, say, PID controllers, in which case
        # I should pass in a list of "complicating variables," then fix
        # them at all time points outside the finite element.
        #
        # Alternative solution is to identify_variables in each constraint
        # that is activated and fix those belonging to a previous finite
        # element. (Should not encounter variables belonging to a future
        # finite element.)
        # ^ This option is easier, less efficient
        #
        # In either case need to record whether variable was previously fixed
        # so I know if I should unfix it or not.

        for t in fe:
            for _slice in dae_vars:
                if not _slice[t].fixed:
                    # Fixed DAE variables are time-dependent disturbances,
                    # whose values should not be altered by this function.
                    _slice[t].set_value(_slice[t_prev].value)

        assert degrees_of_freedom(model) == 0

        with idaeslog.solver_log(solver_log, level=idaeslog.DEBUG) as slc:
            results = solver.solve(model, tee=slc.tee)
        if results.solver.termination_condition == TerminationCondition.optimal:
            pass
        else:
            raise ValueError('Failed to solve for finite element %s' % i)

        for t in fe:
            for comp in deactivated[t]:
                comp.deactivate()

        for var in fixed_vars:
            if not was_originally_fixed[id(var)]:
                var.unfix()

    for t in time:
        for comp in deactivated[t]:
            if was_originally_active[id(comp)]:
                comp.activate()
예제 #26
0
def generate_time_element_blocks(
    m,
    time,
    skip_partition=False,
    flatten_vars=None,
    flatten_cons=None,
    time_subsystems=None,
):
    with TIMER.context("flatten"):
        if flatten_vars is None:
            scalar_vars, dae_vars = flatten_dae_components(m, time, Var)
        else:
            scalar_vars, dae_vars = flatten_vars
        if flatten_cons is None:
            scalar_cons, dae_cons = flatten_dae_components(m, time, Constraint)
        else:
            scalar_cons, dae_cons = flatten_cons
    with TIMER.context("get time subsystems"):
        # We repeat the above flattening in this function...
        if time_subsystems is None:
            subsystems = get_subsystems_along_time(
                m,
                time,
                flatten_vars=(scalar_vars, dae_vars),
                flatten_cons=(scalar_cons, dae_cons),
            )
        else:
            subsystems = time_subsystems
    if not skip_partition:
        # If we know our time-subsystems are all independent, as is
        # the case in a discretization that only involves adjacent
        # time points, then we can skip this expensive partition step.
        with TIMER.context("partition"):
            partition = partition_independent_subsystems(subsystems)
    else:
        partition = [[i] for i in range(len(subsystems))]
    time_partition = [[time.at(i + 1) for i in subset] for subset in partition]

    combined_subsystems = [(
        [con for i in subset for con in subsystems[i][0]],
        [var for i in subset for var in subsystems[i][1]],
    ) for subset in partition]
    TIMER.start("subsystem blocks")
    for i, (block, inputs) in enumerate(
            generate_subsystem_blocks(combined_subsystems)):
        TIMER.stop("subsystem blocks")
        t_points = time_partition[i]
        assert len(block.vars) == len(block.cons)
        if i != 0:
            # Initialize with results of previous solve
            # TODO: Should initialize happen in a separate function?
            for var in dae_vars:
                # The time required to look up the vardata objects
                # in these references is actually showing up in a
                # profile. TODO: Potentially refactor to use
                # dicts: time -> vardata here... (Note that this
                # caches valid time points, as opposed to references)
                vlatest = var[latest]
                for t in t_points:
                    var_t = var[t]
                    if not var_t.fixed:
                        var_t.set_value(vlatest.value)
        yield block, inputs
        # I don't think t_points can be empty. TODO: is this correct?
        latest = t_points[-1]

        if i != len(combined_subsystems) - 1:
            # Small hack so we can profile generate_subsystem_blocks
            # in this loop
            TIMER.start("subsystem blocks")
예제 #27
0
    def _construct(self):
        """ Generates time-indexed references and categorizes them. """
        model = self.mod
        time = self.time
        try:
            inputs = self._inputs
        except AttributeError:
            inputs = self._inputs = None
        try:
            measurements = self._measurements
        except AttributeError:
            measurements = self._measurements = None

        # TODO: Give the user the option to provide their own
        # category_dict (they know the structure of their model
        # better than I do...)
        if self._category_dict is not None:
            category_dict = self.category_dict = self._category_dict
            if (VC.INPUT not in category_dict and inputs is not None):
                self.category_dict[VC.INPUT] = inputs
            if (VC.MEASUREMENT not in category_dict
                    and measurements is not None):
                self.category_dict[VC.MEASUREMENT] = measurements
            self.dae_vars = []
            for categ, varlist in category_dict.items():
                if categ is not VC.MEASUREMENT:
                    # Assume that measurements are duplicates
                    self.dae_vars.extend(varlist)

        else:
            scalar_vars, dae_vars = flatten_dae_components(
                model,
                time,
                ctype=Var,
            )
            self.scalar_vars = scalar_vars
            self.dae_vars = dae_vars
            category_dict = categorize_dae_variables(
                dae_vars,
                time,
                inputs,
                measurements=measurements,
            )
            self.category_dict = category_dict

        keys = list(category_dict)
        for categ in keys:
            if not category_dict[categ]:
                # Empty categories cause problems for us down the road
                # due empty (unknown dimension) slices.
                category_dict.pop(categ)

        self._add_category_blocks()
        self._add_category_references()

        self.categories = set(category_dict)

        # Now that we don't rely on knowing what the categories
        # will be, we do not need these attributes. They are, however,
        # used in the tests, so for now, we add them if possible.
        if VC.DIFFERENTIAL in category_dict:
            self.differential_vars = category_dict[VC.DIFFERENTIAL]
        if VC.ALGEBRAIC in category_dict:
            self.algebraic_vars = category_dict[VC.ALGEBRAIC]
        if VC.DERIVATIVE in category_dict:
            self.derivative_vars = category_dict[VC.DERIVATIVE]
        if VC.INPUT in category_dict:
            self.input_vars = category_dict[VC.INPUT]
        if VC.FIXED in category_dict:
            self.fixed_vars = category_dict[VC.FIXED]
        if VC.MEASUREMENT in category_dict:
            self.measurement_vars = category_dict.pop(VC.MEASUREMENT)

        # The categories in category_dict now form a partition of the
        # time-indexed variables. This is necessary to have a well-defined
        # vardata map, which maps each vardata to a unique component indexed
        # only by time.

        # Maps each vardata (of a time-indexed var) to the NmpcVar
        # that contains it.
        self.vardata_map = ComponentMap(
            (var[t], var)
            for var in self.component_objects(SubclassOf(NmpcVar))
            #for varlist in category_dict.values()
            #for var in varlist
            for t in time if var.ctype is not MeasuredVar)
        # NOTE: looking up var[t] instead of iterating over values()
        # appears to be ~ 5x faster

        # These should be overridden by a call to `set_sample_time`
        # The defaults assume that the entire model is one sample.
        self.sample_points = [time.first(), time.last()]
        self.sample_point_indices = [1, len(time)]
예제 #28
0
    def categorize_variables(model, initial_inputs):
        """Creates lists of time-only-slices of the different types of variables
        in a model, given knowledge of which are inputs. These lists are added 
        as attributes to the model's namespace.

        Possible variable categories are:

            - INPUT --- Those specified by the user to be inputs
            - DERIVATIVE --- Those declared as Pyomo DerivativeVars, whose 
                             "state variable" is not fixed, except possibly as an
                             initial condition
            - DIFFERENTIAL --- Those referenced as the "state variable" by an
                               unfixed (except possibly as an initial condition)
                               DerivativeVar
            - FIXED --- Those that are fixed at non-initial time points. These
                        are typically disturbances, design variables, or 
                        uncertain parameters.
            - ALGEBRAIC --- Unfixed, time-indexed variables that are neither
                            inputs nor referenced by an unfixed derivative.
            - SCALAR --- Variables unindexed by time. These could be variables
                         that refer to a specific point in time (initial or
                         final conditions), averages over time, or truly 
                         time-independent variables like diameter.

        Args:
            model : Model whose variables will be flattened and categorized
            initial_inputs : List of VarData objects that are input variables
                             at the initial time point

        """
        namespace = getattr(model, DynamicBase.get_namespace_name())
        time = namespace.get_time()
        t0 = time.first()
        t1 = time.get_finite_elements()[1]
        deriv_vars = []
        diff_vars = []
        input_vars = []
        alg_vars = []
        fixed_vars = []

        ic_vars = []

        # Create list of time-only-slices of time indexed variables
        # (And list of VarData objects for scalar variables)
        scalar_vars, dae_vars = flatten_dae_components(model, time, Var)

        dae_map = ComponentMap([(v[t0], v) for v in dae_vars])
        t0_vardata = list(dae_map.keys())
        namespace.dae_vars = list(dae_map.values())
        namespace.scalar_vars = \
            NMPCVarGroup(
                list(ComponentMap([(v, v) for v in scalar_vars]).values()),
                index_set=None, is_scalar=True)
        namespace.n_scalar_vars = \
                namespace.scalar_vars.n_vars
        input_set = ComponentSet(initial_inputs)
        updated_input_set = ComponentSet(initial_inputs)

        # Iterate over initial vardata, popping from dae map when an input,
        # derivative, or differential var is found.
        for var0 in t0_vardata:
            if var0 in updated_input_set:
                input_set.remove(var0)
                time_slice = dae_map.pop(var0)
                input_vars.append(time_slice)

            parent = var0.parent_component()
            if not isinstance(parent, DerivativeVar):
                continue
            if not time in ComponentSet(parent.get_continuousset_list()):
                continue
            index0 = var0.index()
            var1 = dae_map[var0][t1]
            index1 = var1.index()
            state = parent.get_state_var()

            if state[index1].fixed:
                # Assume state var is fixed everywhere, so derivative
                # 'isn't really' a derivative.
                # Should be safe to remove state from dae_map here
                state_slice = dae_map.pop(state[index0])
                fixed_vars.append(state_slice)
                continue
            if state[index0] in input_set:
                # If differential variable is an input, then this DerivativeVar
                # is 'not really a derivative'
                continue

            deriv_slice = dae_map.pop(var0)

            if var1.fixed:
                # Assume derivative has been fixed everywhere.
                # Add to list of fixed variables, and don't remove its state variable.
                fixed_vars.append(deriv_slice)
            elif var0.fixed:
                # In this case the derivative has been used as an initial condition.
                # Still want to include it in the list of derivatives.
                ic_vars.append(deriv_slice)
                state_slice = dae_map.pop(state[index0])
                if state[index0].fixed:
                    ic_vars.append(state_slice)
                deriv_vars.append(deriv_slice)
                diff_vars.append(state_slice)
            else:
                # Neither is fixed. This should be the most common case.
                state_slice = dae_map.pop(state[index0])
                if state[index0].fixed:
                    ic_vars.append(state_slice)
                deriv_vars.append(deriv_slice)
                diff_vars.append(state_slice)

        if not updated_input_set:
            raise RuntimeError('Not all inputs could be found')
        assert len(deriv_vars) == len(diff_vars)

        for var0, time_slice in dae_map.items():
            var1 = time_slice[t1]
            # If the variable is still in the list of time-indexed vars,
            # it must either be fixed (not a var) or be an algebraic var
            if var1.fixed:
                fixed_vars.append(time_slice)
            else:
                if var0.fixed:
                    ic_vars.append(time_slice)
                alg_vars.append(time_slice)

        namespace.deriv_vars = NMPCVarGroup(deriv_vars, time)
        namespace.diff_vars = NMPCVarGroup(diff_vars, time)
        namespace.n_diff_vars = len(diff_vars)
        namespace.n_deriv_vars = len(deriv_vars)
        assert (namespace.n_diff_vars == namespace.n_deriv_vars)

        # ic_vars will not be stored as a NMPCVarGroup - don't want to store
        # all the info twice
        namespace.ic_vars = ic_vars
        namespace.n_ic_vars = len(ic_vars)
        #assert model.n_dv == len(ic_vars)
        # Would like this to be true, but accurately detecting differential
        # variables that are not implicitly fixed (by fixing some input)
        # is difficult
        # Also, a categorization can have no input vars and still be
        # valid for MHE

        namespace.input_vars = NMPCVarGroup(input_vars, time)
        namespace.n_input_vars = len(input_vars)

        namespace.alg_vars = NMPCVarGroup(alg_vars, time)
        namespace.n_alg_vars = len(alg_vars)

        namespace.fixed_vars = NMPCVarGroup(fixed_vars, time)
        namespace.n_fixed_vars = len(fixed_vars)

        namespace.variables_categorized = True