Example #1
0
def define_state(b):
    # FpcTP contains full information on the phase equilibrium, so flash
    # calculations re not always needed
    b.always_flash = False

    units = b.params.get_metadata().derived_units
    # Get bounds and initial values from config args
    f_bounds, f_init = get_bounds_from_config(b, "flow_mol_phase_comp",
                                              units["flow_mole"])
    t_bounds, t_init = get_bounds_from_config(b, "temperature",
                                              units["temperature"])
    p_bounds, p_init = get_bounds_from_config(b, "pressure", units["pressure"])

    # Add state variables
    b.flow_mol_phase_comp = Var(b.params._phase_component_set,
                                initialize=f_init,
                                domain=NonNegativeReals,
                                bounds=f_bounds,
                                doc='Phase-component molar flowrate',
                                units=units["flow_mole"])
    b.pressure = Var(initialize=p_init,
                     domain=NonNegativeReals,
                     bounds=p_bounds,
                     doc='State pressure',
                     units=units["pressure"])
    b.temperature = Var(initialize=t_init,
                        domain=NonNegativeReals,
                        bounds=t_bounds,
                        doc='State temperature',
                        units=units["temperature"])

    # Add supporting variables
    b.flow_mol = Expression(expr=sum(b.flow_mol_phase_comp[i]
                                     for i in b.params._phase_component_set),
                            doc="Total molar flowrate")

    def flow_mol_phase(b, p):
        return sum(b.flow_mol_phase_comp[p, j] for j in b.params.component_list
                   if (p, j) in b.params._phase_component_set)

    b.flow_mol_phase = Expression(b.params.phase_list,
                                  rule=flow_mol_phase,
                                  doc='Phase molar flow rates')

    def rule_flow_mol_comp(b, j):
        return sum(b.flow_mol_phase_comp[p, j] for p in b.params.phase_list
                   if (p, j) in b.params._phase_component_set)

    b.flow_mol_comp = Expression(b.params.component_list,
                                 rule=rule_flow_mol_comp,
                                 doc='Component molar flow rates')

    def mole_frac_comp(b, j):
        return (sum(b.flow_mol_phase_comp[p, j] for p in b.params.phase_list
                    if (p, j) in b.params._phase_component_set) / b.flow_mol)

    b.mole_frac_comp = Expression(b.params.component_list,
                                  rule=mole_frac_comp,
                                  doc='Mixture mole fractions')

    b.mole_frac_phase_comp = Var(b.params._phase_component_set,
                                 initialize=1 / len(b.params.component_list),
                                 doc='Phase mole fractions',
                                 units=None)

    def rule_mole_frac_phase_comp(b, p, j):
        return b.mole_frac_phase_comp[p, j] * b.flow_mol_phase[p] == \
            b.flow_mol_phase_comp[p, j]

    b.mole_frac_phase_comp_eq = Constraint(b.params._phase_component_set,
                                           rule=rule_mole_frac_phase_comp)

    def rule_phase_frac(b, p):
        if len(b.params.phase_list) == 1:
            return 1
        else:
            return b.flow_mol_phase[p] / b.flow_mol

    b.phase_frac = Expression(b.params.phase_list,
                              rule=rule_phase_frac,
                              doc='Phase fractions')

    # -------------------------------------------------------------------------
    # General Methods
    def get_material_flow_terms_FpcTP(p, j):
        """Create material flow terms for control volume."""
        return b.flow_mol_phase_comp[p, j]

    b.get_material_flow_terms = get_material_flow_terms_FpcTP

    def get_enthalpy_flow_terms_FpcTP(p):
        """Create enthalpy flow terms."""
        return b.flow_mol_phase[p] * b.enth_mol_phase[p]

    b.get_enthalpy_flow_terms = get_enthalpy_flow_terms_FpcTP

    def get_material_density_terms_FpcTP(p, j):
        """Create material density terms."""
        if j in b.params.component_list:
            return b.dens_mol_phase[p] * b.mole_frac_phase_comp[p, j]
        else:
            return 0

    b.get_material_density_terms = get_material_density_terms_FpcTP

    def get_energy_density_terms_FpcTP(p):
        """Create energy density terms."""
        return b.dens_mol_phase[p] * b.enth_mol_phase[p]

    b.get_energy_density_terms = get_energy_density_terms_FpcTP

    def default_material_balance_type_FpcTP():
        return MaterialBalanceType.componentTotal

    b.default_material_balance_type = default_material_balance_type_FpcTP

    def default_energy_balance_type_FpcTP():
        return EnergyBalanceType.enthalpyTotal

    b.default_energy_balance_type = default_energy_balance_type_FpcTP

    def get_material_flow_basis_FpcTP():
        return MaterialFlowBasis.molar

    b.get_material_flow_basis = get_material_flow_basis_FpcTP

    def define_state_vars_FpcTP():
        """Define state vars."""
        return {
            "flow_mol_phase_comp": b.flow_mol_phase_comp,
            "temperature": b.temperature,
            "pressure": b.pressure
        }

    b.define_state_vars = define_state_vars_FpcTP

    def define_display_vars_FpcTP():
        """Define display vars."""
        return {
            "Molar Flowrate": b.flow_mol_phase_comp,
            "Temperature": b.temperature,
            "Pressure": b.pressure
        }

    b.define_display_vars = define_display_vars_FpcTP
Example #2
0
def define_state(b):
    # FcTP formulation always requires a flash, so set flag to True
    # TODO: should have some checking to make sure developers implement this properly
    b.always_flash = True

    # Check that only necessary state_bounds are defined
    expected_keys = ["flow_mol_comp", "temperature", "pressure"]
    if (b.params.config.state_bounds is not None
            and any(b.params.config.state_bounds.keys()) not in expected_keys):
        for k in b.params.config.state_bounds.keys():
            if k not in expected_keys:
                raise ConfigurationError(
                    "{} - found unexpected state_bounds key {}. Please ensure "
                    "bounds are provided only for expected state variables "
                    "and that you have typed the variable names correctly.".
                    format(b.name, k))

    units = b.params.get_metadata().derived_units
    # Get bounds and initial values from config args
    f_bounds, f_init = get_bounds_from_config(b, "flow_mol_comp",
                                              units["flow_mole"])
    t_bounds, t_init = get_bounds_from_config(b, "temperature",
                                              units["temperature"])
    p_bounds, p_init = get_bounds_from_config(b, "pressure", units["pressure"])

    # Add state variables
    b.flow_mol_comp = Var(b.component_list,
                          initialize=f_init,
                          domain=NonNegativeReals,
                          bounds=f_bounds,
                          doc=' Component molar flowrate',
                          units=units["flow_mole"])
    b.pressure = Var(initialize=p_init,
                     domain=NonNegativeReals,
                     bounds=p_bounds,
                     doc='State pressure',
                     units=units["pressure"])
    b.temperature = Var(initialize=t_init,
                        domain=NonNegativeReals,
                        bounds=t_bounds,
                        doc='State temperature',
                        units=units["temperature"])

    # Add supporting variables
    b.flow_mol = Expression(expr=sum(b.flow_mol_comp[j]
                                     for j in b.component_list),
                            doc="Total molar flowrate")

    if f_init is None:
        fp_init = None
    else:
        fp_init = f_init / len(b.phase_list)

    b.flow_mol_phase = Var(b.phase_list,
                           initialize=fp_init,
                           domain=NonNegativeReals,
                           bounds=f_bounds,
                           doc='Phase molar flow rates',
                           units=units["flow_mole"])

    b.mole_frac_comp = Var(b.component_list,
                           bounds=(0, None),
                           initialize=1 / len(b.component_list),
                           doc='Mixture mole fractions',
                           units=None)

    b.mole_frac_phase_comp = Var(b.phase_component_set,
                                 initialize=1 / len(b.component_list),
                                 bounds=(0, None),
                                 doc='Phase mole fractions',
                                 units=None)

    b.phase_frac = Var(b.phase_list,
                       initialize=1 / len(b.phase_list),
                       bounds=(0, None),
                       doc='Phase fractions',
                       units=None)

    # Add supporting constraints
    def rule_mole_frac_comp(b, j):
        if len(b.component_list) > 1:
            return b.flow_mol_comp[j] == b.mole_frac_comp[j] * sum(
                b.flow_mol_comp[k] for k in b.component_list)
        else:
            return b.mole_frac_comp[j] == 1

    b.mole_frac_comp_eq = Constraint(b.component_list,
                                     rule=rule_mole_frac_comp)

    if len(b.phase_list) == 1:

        def rule_total_mass_balance(b):
            return b.flow_mol_phase[b.phase_list[1]] == b.flow_mol

        b.total_flow_balance = Constraint(rule=rule_total_mass_balance)

        def rule_comp_mass_balance(b, i):
            return b.mole_frac_comp[i]*1e3 == \
                1e3*b.mole_frac_phase_comp[b.phase_list[1], i]

        b.component_flow_balances = Constraint(b.component_list,
                                               rule=rule_comp_mass_balance)

        def rule_phase_frac(b, p):
            return b.phase_frac[p] == 1

        b.phase_fraction_constraint = Constraint(b.phase_list,
                                                 rule=rule_phase_frac)

    elif len(b.phase_list) == 2:
        # For two phase, use Rachford-Rice formulation
        def rule_total_mass_balance(b):
            return sum(b.flow_mol_phase[p] for p in b.phase_list) == \
                b.flow_mol

        b.total_flow_balance = Constraint(rule=rule_total_mass_balance)

        def rule_comp_mass_balance(b, i):
            return b.flow_mol_comp[i] == sum(
                b.flow_mol_phase[p] * b.mole_frac_phase_comp[p, i]
                for p in b.phase_list if (p, i) in b.phase_component_set)

        b.component_flow_balances = Constraint(b.component_list,
                                               rule=rule_comp_mass_balance)

        def rule_mole_frac(b):
            return 1e3*sum(b.mole_frac_phase_comp[b.phase_list[1], i]
                           for i in b.component_list
                           if (b.phase_list[1], i)
                           in b.phase_component_set) -\
                1e3*sum(b.mole_frac_phase_comp[b.phase_list[2], i]
                        for i in b.component_list
                        if (b.phase_list[2], i)
                        in b.phase_component_set) == 0

        b.sum_mole_frac = Constraint(rule=rule_mole_frac)

        def rule_phase_frac(b, p):
            return b.phase_frac[p] * b.flow_mol == b.flow_mol_phase[p]

        b.phase_fraction_constraint = Constraint(b.phase_list,
                                                 rule=rule_phase_frac)

    else:
        # Otherwise use a general formulation
        def rule_comp_mass_balance(b, i):
            return b.flow_mol_comp[i] == sum(
                b.flow_mol_phase[p] * b.mole_frac_phase_comp[p, i]
                for p in b.phase_list if (p, i) in b.phase_component_set)

        b.component_flow_balances = Constraint(b.component_list,
                                               rule=rule_comp_mass_balance)

        def rule_mole_frac(b, p):
            return 1e3 * sum(b.mole_frac_phase_comp[p, i]
                             for i in b.component_list
                             if (p, i) in b.phase_component_set) == 1e3

        b.sum_mole_frac = Constraint(b.phase_list, rule=rule_mole_frac)

        def rule_phase_frac(b, p):
            return b.phase_frac[p] * b.flow_mol == b.flow_mol_phase[p]

        b.phase_fraction_constraint = Constraint(b.phase_list,
                                                 rule=rule_phase_frac)

    # -------------------------------------------------------------------------
    # General Methods
    def get_material_flow_terms_FTPx(p, j):
        """Create material flow terms for control volume."""
        if j in b.component_list:
            return b.flow_mol_phase[p] * b.mole_frac_phase_comp[p, j]
        else:
            return 0

    b.get_material_flow_terms = get_material_flow_terms_FTPx

    def get_enthalpy_flow_terms_FTPx(p):
        """Create enthalpy flow terms."""
        return b.flow_mol_phase[p] * b.enth_mol_phase[p]

    b.get_enthalpy_flow_terms = get_enthalpy_flow_terms_FTPx

    def get_material_density_terms_FTPx(p, j):
        """Create material density terms."""
        if j in b.component_list:
            return b.dens_mol_phase[p] * b.mole_frac_phase_comp[p, j]
        else:
            return 0

    b.get_material_density_terms = get_material_density_terms_FTPx

    def get_energy_density_terms_FTPx(p):
        """Create energy density terms."""
        return b.dens_mol_phase[p] * b.enth_mol_phase[p]

    b.get_energy_density_terms = get_energy_density_terms_FTPx

    def default_material_balance_type_FTPx():
        return MaterialBalanceType.componentTotal

    b.default_material_balance_type = default_material_balance_type_FTPx

    def default_energy_balance_type_FTPx():
        return EnergyBalanceType.enthalpyTotal

    b.default_energy_balance_type = default_energy_balance_type_FTPx

    def get_material_flow_basis_FTPx():
        return MaterialFlowBasis.molar

    b.get_material_flow_basis = get_material_flow_basis_FTPx

    def define_state_vars_FTPx():
        """Define state vars."""
        return {
            "flow_mol_comp": b.flow_mol_comp,
            "temperature": b.temperature,
            "pressure": b.pressure
        }

    b.define_state_vars = define_state_vars_FTPx

    def define_display_vars_FTPx():
        """Define display vars."""
        return {
            "Molar Flowrate": b.flow_mol_comp,
            "Temperature": b.temperature,
            "Pressure": b.pressure
        }

    b.define_display_vars = define_display_vars_FTPx
Example #3
0
    def common(b, pobj):
        pname = pobj.local_name

        molecular_set = b.params.solvent_set | b.params.solute_set

        # Check options for alpha rule
        if (pobj.config.equation_of_state_options is not None
                and "alpha_rule" in pobj.config.equation_of_state_options):
            alpha_rule = pobj.config.equation_of_state_options[
                "alpha_rule"].return_expression
        else:
            alpha_rule = DefaultAlphaRule.return_expression

        # Check options for tau rule
        if (pobj.config.equation_of_state_options is not None
                and "tau_rule" in pobj.config.equation_of_state_options):
            tau_rule = pobj.config.equation_of_state_options[
                "tau_rule"].return_expression
        else:
            tau_rule = DefaultTauRule.return_expression

        # Check options for reference state
        if (pobj.config.equation_of_state_options is not None and
                "reference_state" in pobj.config.equation_of_state_options):
            ref_state = pobj.config.equation_of_state_options[
                "reference_state"]
        else:
            ref_state = DefaultRefState

        # ---------------------------------------------------------------------
        # Calculate composition terms at both actual and reference state
        # Calculate reference state mole fractions
        ref_state.ref_state(b, pname)

        # Ionic Strength
        def rule_I(b):  # Eqn 62
            return (0.5 * sum(b.mole_frac_phase_comp_true[pname, c] *
                              b.params.get_component(c).config.charge**2
                              for c in b.params.ion_set))

        b.add_component(pname + "_ionic_strength",
                        Expression(rule=rule_I, doc="Ionic strength"))

        def rule_I_ref(b):  # Eqn 62 evaluated at reference state
            x = getattr(b, pname + "_x_ref")
            return (0.5 * sum(x[c] * b.params.get_component(c).config.charge**2
                              for c in b.params.ion_set))

        b.add_component(
            pname + "_ionic_strength_ref",
            Expression(rule=rule_I_ref,
                       doc="Ionic strength at reference state"))

        # Calculate mixing factors
        def rule_X(b, j):  # Eqn 21
            if (pname, j) not in b.params.true_phase_component_set:
                return Expression.Skip
            elif j in b.params.cation_set or j in b.params.anion_set:
                return (b.mole_frac_phase_comp_true[pname, j] *
                        abs(cobj(b, j).config.charge))
            else:
                return b.mole_frac_phase_comp_true[pname, j]

        b.add_component(
            pname + "_X",
            Expression(b.params.true_species_set,
                       rule=rule_X,
                       doc="Charge x mole fraction term"))

        def rule_X_ref(b, j):  # Eqn 21 evaluated at reference state
            x = getattr(b, pname + "_x_ref")
            if (pname, j) not in b.params.true_phase_component_set:
                return Expression.Skip
            elif j in b.params.cation_set or j in b.params.anion_set:
                return (x[j] * abs(cobj(b, j).config.charge))
            else:
                return x[j]

        b.add_component(
            pname + "_X_ref",
            Expression(b.params.true_species_set,
                       rule=rule_X_ref,
                       doc="Charge x mole fraction term at reference state"))

        def rule_Y(b, j):
            if cobj(b, j).config.charge < 0:
                # Anion
                dom = b.params.anion_set
            else:
                dom = b.params.cation_set

            X = getattr(b, pname + "_X")
            return X[j] / sum(X[i] for i in dom)  # Eqns 36 and 37

        # Y is a charge ratio, and thus independent of x for symmetric state
        # TODO: This may need to change for the unsymmetric state
        b.add_component(
            pname + "_Y",
            Expression(b.params.ion_set, rule=rule_Y,
                       doc="Charge composition"))

        # ---------------------------------------------------------------------
        # Long-range terms
        # Average molar volume of solvent
        def rule_vol_mol_solvent(b):  # Eqn 77
            if len(b.params.solvent_set) == 1:
                s = b.params.solvent_set.first()
                return ENRTL.get_vol_mol_pure(b, "liq", s, b.temperature)
            else:
                return (sum(b.mole_frac_phase_comp_true[pname, s] *
                            ENRTL.get_vol_mol_pure(b, "liq", s, b.temperature)
                            for s in b.params.solvent_set) /
                        sum(b.mole_frac_phase_comp_true[pname, s]
                            for s in b.params.solvent_set))

        b.add_component(
            pname + "_vol_mol_solvent",
            Expression(rule=rule_vol_mol_solvent,
                       doc="Mean molar volume of solvent"))

        # Mean relative permitivity of solvent
        def rule_eps_solvent(b):  # Eqn 78
            if len(b.params.solvent_set) == 1:
                s = b.params.solvent_set.first()
                return get_method(b, "relative_permittivity_liq_comp",
                                  s)(b, cobj(b, s), b.temperature)
            else:
                return (sum(b.mole_frac_phase_comp_true[pname, s] *
                            get_method(b, "relative_permittivity_liq_comp", s)
                            (b, cobj(b, s), b.temperature) *
                            b.params.get_component(s).mw
                            for s in b.params.solvent_set) /
                        sum(b.mole_frac_phase_comp_true[pname, s] *
                            b.params.get_component(s).mw
                            for s in b.params.solvent_set))

        b.add_component(
            pname + "_relative_permittivity_solvent",
            Expression(rule=rule_eps_solvent,
                       doc="Mean relative permittivity  of solvent"))

        # Debye-Huckel parameter
        def rule_A_DH(b):  # Eqn 61
            # Note: Where the paper refers to the dielectric constant, it
            # actually means the electric permittivity of the solvent
            # eps = eps_r*eps_0 (units F/m)
            # Note that paper is missing a required 4*pi term
            v = pyunits.convert(getattr(b, pname + "_vol_mol_solvent"),
                                pyunits.m**3 / pyunits.mol)
            eps = getattr(b, pname + "_relative_permittivity_solvent")
            eps0 = Constants.vacuum_electric_permittivity
            return ((1 / 3) *
                    (2 * Constants.pi * Constants.avogadro_number / v)**0.5 *
                    (Constants.elemental_charge**2 /
                     (4 * Constants.pi * eps * eps0 *
                      Constants.boltzmann_constant * b.temperature))**(3 / 2))

        b.add_component(
            pname + "_A_DH",
            Expression(rule=rule_A_DH, doc="Debye-Huckel parameter"))

        # Long-range (PDH) contribution to activity coefficient
        def rule_log_gamma_pdh(b, j):
            A = getattr(b, pname + "_A_DH")
            Ix = getattr(b, pname + "_ionic_strength")
            I0 = getattr(b, pname + "_ionic_strength_ref")
            rho = ClosestApproach
            if j in molecular_set:
                # Eqn 69
                # Note typo in original paper. Correct power for I is (3/2)
                return (2 * A * Ix**(3 / 2) / (1 + rho * Ix**(1 / 2)))
            elif j in b.params.ion_set:
                # Eqn 70
                z = abs(cobj(b, j).config.charge)
                return (-A *
                        ((2 * z**2 / rho) * log(
                            (1 + rho * Ix**0.5) / (1 + rho * I0**0.5)) +
                         (z**2 * Ix**0.5 - 2 * Ix**(3 / 2)) /
                         (1 + rho * Ix**0.5) - (2 * Ix * I0**-0.5) /
                         (1 + rho * I0**0.5) * ref_state.ndIdn(b, pname, j)))
            else:
                raise BurntToast(
                    "{} eNRTL model encountered unexpected component.".format(
                        b.name))

        b.add_component(
            pname + "_log_gamma_pdh",
            Expression(b.params.true_species_set,
                       rule=rule_log_gamma_pdh,
                       doc="Long-range contribution to activity coefficient"))

        # ---------------------------------------------------------------------
        # Local Contribution Terms
        # For the symmetric state, all of these are independent of composition
        # TODO: For the unsymmetric state, it may be necessary to recalculate
        # Calculate alphas for all true species pairings
        def rule_alpha_expr(b, i, j):
            Y = getattr(b, pname + "_Y")
            if ((pname, i) not in b.params.true_phase_component_set
                    or (pname, j) not in b.params.true_phase_component_set):
                return Expression.Skip
            elif ((i in molecular_set) and (j in molecular_set)):
                # alpha equal user provided parameters
                return alpha_rule(b, pobj, i, j, b.temperature)
            elif (i in b.params.cation_set and j in molecular_set):
                # Eqn 32
                return sum(
                    Y[k] *
                    alpha_rule(b, pobj, (i + ", " + k), j, b.temperature)
                    for k in b.params.anion_set)
            elif (j in b.params.cation_set and i in molecular_set):
                # Eqn 32
                return sum(
                    Y[k] *
                    alpha_rule(b, pobj, (j + ", " + k), i, b.temperature)
                    for k in b.params.anion_set)
            elif (i in b.params.anion_set and j in molecular_set):
                # Eqn 33
                return sum(
                    Y[k] *
                    alpha_rule(b, pobj, (k + ", " + i), j, b.temperature)
                    for k in b.params.cation_set)
            elif (j in b.params.anion_set and i in molecular_set):
                # Eqn 33
                return sum(
                    Y[k] *
                    alpha_rule(b, pobj, (k + ", " + j), i, b.temperature)
                    for k in b.params.cation_set)
            elif (i in b.params.cation_set and j in b.params.anion_set):
                # Eqn 34
                if len(b.params.cation_set) > 1:
                    return sum(Y[k] * alpha_rule(b, pobj, (i + ", " + j),
                                                 (k + ", " + j), b.temperature)
                               for k in b.params.cation_set)
                else:
                    return 0.2
            elif (i in b.params.anion_set and j in b.params.cation_set):
                # Eqn 35
                if len(b.params.anion_set) > 1:
                    return sum(Y[k] * alpha_rule(b, pobj, (j + ", " + i),
                                                 (j + ", " + k), b.temperature)
                               for k in b.params.anion_set)
                else:
                    return 0.2
            elif ((i in b.params.cation_set and j in b.params.cation_set)
                  or (i in b.params.anion_set and j in b.params.anion_set)):
                # No like-ion interactions
                return Expression.Skip
            else:
                raise BurntToast(
                    "{} eNRTL model encountered unexpected component pair {}.".
                    format(b.name, (i, j)))

        b.add_component(
            pname + "_alpha",
            Expression(b.params.true_species_set,
                       b.params.true_species_set,
                       rule=rule_alpha_expr,
                       doc="Non-randomness parameters"))

        # Calculate G terms
        def rule_G_expr(b, i, j):
            Y = getattr(b, pname + "_Y")

            def _G_appr(b, pobj, i, j, T):  # Eqn 23
                if i != j:
                    return exp(-alpha_rule(b, pobj, i, j, T) *
                               tau_rule(b, pobj, i, j, T))
                else:
                    return 1

            if ((pname, i) not in b.params.true_phase_component_set
                    or (pname, j) not in b.params.true_phase_component_set):
                return Expression.Skip
            elif ((i in molecular_set) and (j in molecular_set)):
                # G comes directly from parameters
                return _G_appr(b, pobj, i, j, b.temperature)
            elif (i in b.params.cation_set and j in molecular_set):
                # Eqn 38
                return sum(Y[k] *
                           _G_appr(b, pobj, (i + ", " + k), j, b.temperature)
                           for k in b.params.anion_set)
            elif (i in molecular_set and j in b.params.cation_set):
                # Eqn 40
                return sum(Y[k] *
                           _G_appr(b, pobj, i, (j + ", " + k), b.temperature)
                           for k in b.params.anion_set)
            elif (i in b.params.anion_set and j in molecular_set):
                # Eqn 39
                return sum(Y[k] *
                           _G_appr(b, pobj, (k + ", " + i), j, b.temperature)
                           for k in b.params.cation_set)
            elif (i in molecular_set and j in b.params.anion_set):
                # Eqn 41
                return sum(Y[k] *
                           _G_appr(b, pobj, i, (k + ", " + j), b.temperature)
                           for k in b.params.cation_set)
            elif (i in b.params.cation_set and j in b.params.anion_set):
                # Eqn 42
                if len(b.params.cation_set) > 1:
                    return sum(Y[k] * _G_appr(b, pobj, (i + ", " + j),
                                              (k + ", " + j), b.temperature)
                               for k in b.params.cation_set)
                else:
                    # This term does not exist for single cation systems
                    # However, need a valid result to calculate tau
                    return 1
            elif (i in b.params.anion_set and j in b.params.cation_set):
                # Eqn 43
                if len(b.params.anion_set) > 1:
                    return sum(Y[k] * _G_appr(b, pobj, (j + ", " + i),
                                              (j + ", " + k), b.temperature)
                               for k in b.params.anion_set)
                else:
                    # This term does not exist for single anion systems
                    # However, need a valid result to calculate tau
                    return 1
            elif ((i in b.params.cation_set and j in b.params.cation_set)
                  or (i in b.params.anion_set and j in b.params.anion_set)):
                # No like-ion interactions
                return Expression.Skip
            else:
                raise BurntToast(
                    "{} eNRTL model encountered unexpected component pair {}.".
                    format(b.name, (i, j)))

        b.add_component(
            pname + "_G",
            Expression(b.params.true_species_set,
                       b.params.true_species_set,
                       rule=rule_G_expr,
                       doc="Local interaction G term"))

        # Calculate tau terms
        def rule_tau_expr(b, i, j):
            if ((pname, i) not in b.params.true_phase_component_set
                    or (pname, j) not in b.params.true_phase_component_set):
                return Expression.Skip
            elif ((i in molecular_set) and (j in molecular_set)):
                # tau equal to parameter
                return tau_rule(b, pobj, i, j, b.temperature)
            elif ((i in b.params.cation_set and j in b.params.cation_set)
                  or (i in b.params.anion_set and j in b.params.anion_set)):
                # No like-ion interactions
                return Expression.Skip
            else:
                alpha = getattr(b, pname + "_alpha")
                G = getattr(b, pname + "_G")
                # Eqn 44
                return -log(G[i, j]) / alpha[i, j]

        b.add_component(
            pname + "_tau",
            Expression(b.params.true_species_set,
                       b.params.true_species_set,
                       rule=rule_tau_expr,
                       doc="Binary interaction energy parameters"))

        # Local contribution to activity coefficient
        def rule_log_gamma_lc_I(b, s):
            X = getattr(b, pname + "_X")
            G = getattr(b, pname + "_G")
            tau = getattr(b, pname + "_tau")

            return log_gamma_lc(b, pname, s, X, G, tau)

        b.add_component(
            pname + "_log_gamma_lc_I",
            Expression(b.params.true_species_set,
                       rule=rule_log_gamma_lc_I,
                       doc="Local contribution at actual state"))

        def rule_log_gamma_lc_I0(b, s):
            X = getattr(b, pname + "_X_ref")
            G = getattr(b, pname + "_G")
            tau = getattr(b, pname + "_tau")

            return log_gamma_lc(b, pname, s, X, G, tau)

        b.add_component(
            pname + "_log_gamma_lc_I0",
            Expression(b.params.ion_set,
                       rule=rule_log_gamma_lc_I0,
                       doc="Local contribution at reference state"))

        def rule_log_gamma_lc(b, s):
            log_gamma_lc_I = getattr(b, pname + "_log_gamma_lc_I")
            if s in molecular_set:
                return log_gamma_lc_I[s]
            else:
                log_gamma_lc_I0 = getattr(b, pname + "_log_gamma_lc_I0")
                return log_gamma_lc_I[s] - log_gamma_lc_I0[s]

        b.add_component(
            pname + "_log_gamma_lc",
            Expression(
                b.params.true_species_set,
                rule=rule_log_gamma_lc,
                doc="Local contribution contribution to activity coefficient"))

        # Overall log gamma
        def rule_log_gamma(b, j):
            pdh = getattr(b, pname + "_log_gamma_pdh")
            lc = getattr(b, pname + "_log_gamma_lc")
            return pdh[j] + lc[j]

        b.add_component(
            pname + "_log_gamma",
            Expression(b.params.true_species_set,
                       rule=rule_log_gamma,
                       doc="Log of activity coefficient"))

        # Activity coefficient of apparent species
        def rule_log_gamma_pm(b, j):
            cobj = b.params.get_component(j)

            if "dissociation_species" in cobj.config:
                dspec = cobj.config.dissociation_species

                n = 0
                d = 0
                for s in dspec:
                    dobj = b.params.get_component(s)
                    ln_g = getattr(b, pname + "_log_gamma")[s]
                    n += abs(dobj.config.charge) * ln_g
                    d += abs(dobj.config.charge)

                return n / d
            else:
                return getattr(b, pname + "_log_gamma")[j]

        b.add_component(
            pname + "_log_gamma_appr",
            Expression(b.params.apparent_species_set,
                       rule=rule_log_gamma_pm,
                       doc="Log of mean activity coefficient"))
Example #4
0
    print(value(m.AverageCost))


#####################
# Expressions


#total CO2 emitted
def co2_total_tons_rule(m):
    total_tons = sum(
        (m.co2_per_mwh[g] * m.DispatchGen[g, t] * m.timepoint_duration)
        for g in m.GENERATORS for t in m.TIMEPOINTS)
    return total_tons


m.co2_total_tons = Expression(rule=co2_total_tons_rule)


def report_results(m):
    print(value(m.co2_total_tons))


# curtail
def curtailed_energy_rule(m, g, t):
    total_curtail = ((m.BuildGen[g] * m.max_cf[g, t] - m.DispatchGen[g, t]))
    return total_curtail


m.curtailed_energy = Expression(m.GENERATORS,
                                m.TIMEPOINTS,
                                rule=curtailed_energy_rule)
Example #5
0
def add_model_components(m, d, scenario_directory, subproblem, stage):
    """
    The following Pyomo model components are defined in this module:

    +-------------------------------------------------------------------------+
    | Sets                                                                    |
    +=========================================================================+
    | | :code:`FUEL_PRJ_OPR_TMPS`                                             |
    | | *Within*: :code:`PRJ_OPR_TMPS`                                        |
    |                                                                         |
    | The two-dimensional set of projects for which a fuel is specified and   |
    | their operational timepoints.                                           |
    +-------------------------------------------------------------------------+
    | | :code:`HR_CURVE_PRJS_OPR_TMPS_SGMS`                                   |
    |                                                                         |
    | The three-dimensional set of projects for which a heat rate curve is    |
    | specified along with the heat rate curve segments and the project       |
    | operational timepoints.                                                 |
    +-------------------------------------------------------------------------+
    | | :code:`HR_CURVE_PRJS_OPR_TMPS`                                        |
    | | *Within*: :code:`PRJ_OPR_TMPS`                                        |
    |                                                                         |
    | The two-dimensional set of projects for which a heat rate curve is      |
    | specified along with their operational timepoints.                      |
    +-------------------------------------------------------------------------+
    | | :code:`STARTUP_FUEL_PRJ_OPR_TMPS`                                     |
    | | *Within*: :code:`FUEL_PRJ_OPR_TMPS`                                   |
    |                                                                         |
    | The two-dimensional set of projects for which startup fuel burn is      |
    | specified and their operational timepoints.                             |
    +-------------------------------------------------------------------------+

    |                                                                         |

    +-------------------------------------------------------------------------+
    | Variables                                                               |
    +=========================================================================+
    | | :code:`HR_Curve_Prj_Fuel_Burn`                                        |
    | | *Defined over*: :code:`HR_CURVE_PRJS_OPR_TMPS`                        |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | Fuel burn in each operational timepoint of projects with a heat rate    |
    | curve.                                                                  |
    +-------------------------------------------------------------------------+

    |                                                                         |

    +-------------------------------------------------------------------------+
    | Constraints                                                             |
    +=========================================================================+
    | | :code:`HR_Curve_Prj_Fuel_Burn_Constraint`                             |
    | | *Defined over*: :code:`HR_CURVE_PRJS_OPR_TMPS_SGMS`                   |
    |                                                                         |
    | Determines fuel burn from the project in each timepoint based on its    |
    | heat rate curve.                                                        |
    +-------------------------------------------------------------------------+

    |                                                                         |

    +-------------------------------------------------------------------------+
    | Expressions                                                             |
    +=========================================================================+
    | | :code:`Operations_Fuel_Burn_MMBtu`                                    |
    | | *Within*: :code:`PRJ_OPR_TMPS`                                        |
    |                                                                         |
    | This expression describes each project's operational fuel consumption   |
    | (in MMBtu) in all operational timepoints. We obtain it by calling the   |
    | *fuel_burn_rule* method in the relevant *operational_type*. This does   |
    | not include fuel burn for startups, which has a separate expression.    |
    +-------------------------------------------------------------------------+
    | | :code:`Startup_Fuel_Burn_MMBtu`                                       |
    | | *Within*: :code:`PRJ_OPR_TMPS`                                        |
    |                                                                         |
    | This expression describes each project's startup fuel consumption       |
    | (in MMBtu) in all operational timepoints. We obtain it by calling the   |
    | *startup_fuel_burn_rule* method in the relevant *operational_type*.     |
    | Only operational types with commitment variables can have startup fuel  |
    | burn (for others it will always return zero).                           |
    +-------------------------------------------------------------------------+
    | | :code:`Total_Fuel_Burn_MMBtu`                                         |
    | | *Within*: :code:`PRJ_OPR_TMPS`                                        |
    |                                                                         |
    | Total fuel burn is the sum of operational fuel burn for power           |
    | production and startup fuel burn.                                       |
    +-------------------------------------------------------------------------+

    """

    # Dynamic Inputs
    ###########################################################################

    required_operational_modules = get_required_subtype_modules_from_projects_file(
        scenario_directory=scenario_directory, subproblem=subproblem,
        stage=stage, which_type="operational_type"
    )

    imported_operational_modules = load_operational_type_modules(
        required_operational_modules
    )

    # Sets
    ###########################################################################

    m.FUEL_PRJ_OPR_TMPS = Set(
        dimen=2,
        within=m.PRJ_OPR_TMPS,
        initialize=lambda mod: [(p, tmp) for (p, tmp) in mod.PRJ_OPR_TMPS
                          if p in mod.FUEL_PRJS]
    )

    m.HR_CURVE_PRJS_OPR_TMPS_SGMS = Set(
        dimen=3,
        initialize=lambda mod:
        set((g, tmp, s) for (g, tmp) in mod.PRJ_OPR_TMPS
            for _g, p, s in mod.HR_CURVE_PRJS_PRDS_SGMS
            if g == _g and mod.period[tmp] == p)
    )

    m.HR_CURVE_PRJS_OPR_TMPS = Set(
        dimen=2,
        within=m.FUEL_PRJ_OPR_TMPS,
        initialize=lambda mod:
        set((g, tmp)
            for (g, tmp, s) in mod.HR_CURVE_PRJS_OPR_TMPS_SGMS)
    )

    m.STARTUP_FUEL_PRJ_OPR_TMPS = Set(
        dimen=2,
        within=m.FUEL_PRJ_OPR_TMPS,
        initialize=lambda mod: [(p, tmp) for (p, tmp) in mod.FUEL_PRJ_OPR_TMPS
                          if p in mod.STARTUP_FUEL_PRJS]
    )

    # Variables
    ###########################################################################

    m.HR_Curve_Prj_Fuel_Burn = Var(
        m.HR_CURVE_PRJS_OPR_TMPS,
        within=NonNegativeReals
    )

    # Constraints
    ###########################################################################

    def fuel_burn_by_ll_constraint_rule(mod, prj, tmp, s):
        """
        **Constraint Name**: HR_Curve_Prj_Fuel_Burn_Constraint
        **Enforced Over**: HR_CURVE_PRJS_OPR_TMPS_SGMS

        Fuel burn is set by piecewise linear representation of input/output
        curve.

        Note: we assume that when projects are de-rated for availability, the
        input/output curve is de-rated by the same amount. The implicit
        assumption is that when a generator is de-rated, some of its units
        are out rather than it being forced to run below minimum stable level
        at very inefficient operating points.
        """
        gen_op_type = mod.operational_type[prj]
        if hasattr(imported_operational_modules[gen_op_type],
                   "fuel_burn_by_ll_rule"):
            fuel_burn_by_ll = imported_operational_modules[gen_op_type]. \
                fuel_burn_by_ll_rule(mod, prj, tmp, s)
        else:
            fuel_burn_by_ll = \
                op_type.fuel_burn_by_ll_rule(mod, prj, tmp, s)

        return mod.HR_Curve_Prj_Fuel_Burn[prj, tmp] >= fuel_burn_by_ll

    m.HR_Curve_Prj_Fuel_Burn_Constraint = Constraint(
        m.HR_CURVE_PRJS_OPR_TMPS_SGMS,
        rule=fuel_burn_by_ll_constraint_rule
    )

    # Expressions
    ###########################################################################

    def fuel_burn_rule(mod, prj, tmp):
        """
        Emissions from each project based on operational type
        (and whether a project burns fuel)
        """
        gen_op_type = mod.operational_type[prj]
        if hasattr(imported_operational_modules[gen_op_type],
                   "fuel_burn_rule"):
            fuel_burn_simple = imported_operational_modules[gen_op_type]. \
                fuel_burn_rule(mod, prj, tmp)
        else:
            fuel_burn_simple = op_type.fuel_burn_rule(mod, prj, tmp)

        return fuel_burn_simple \
            + (mod.HR_Curve_Prj_Fuel_Burn[prj, tmp] if prj in mod.HR_CURVE_PRJS
               else 0)

    m.Operations_Fuel_Burn_MMBtu = Expression(
        m.FUEL_PRJ_OPR_TMPS,
        rule=fuel_burn_rule
    )

    def startup_fuel_burn_rule(mod, prj, tmp):
        """
        Startup fuel burn is defined for some operational types while
        they are zero for others. Get the appropriate expression for each
        generator based on its operational type.
        """
        gen_op_type = mod.operational_type[prj]
        if hasattr(imported_operational_modules[gen_op_type],
                   "startup_fuel_burn_rule"):
            return imported_operational_modules[gen_op_type]. \
                startup_fuel_burn_rule(mod, prj, tmp)
        else:
            return op_type.startup_fuel_burn_rule(mod, prj, tmp)

    m.Startup_Fuel_Burn_MMBtu = Expression(
        m.STARTUP_FUEL_PRJ_OPR_TMPS,
        rule=startup_fuel_burn_rule
    )

    def total_fuel_burn_rule(mod, g, tmp):
        """
        *Expression Name*: :code:`Total_Fuel_Burn_MMBtu`
        *Defined Over*: :code:`PRJ_OPR_TMPS`

        Total fuel burn is the sum of operational fuel burn (power production)
        and startup fuel burn.
        """
        return mod.Operations_Fuel_Burn_MMBtu[g, tmp] \
            + (mod.Startup_Fuel_Burn_MMBtu[g, tmp]
               if g in mod.STARTUP_FUEL_PRJS
               else 0)

    m.Total_Fuel_Burn_MMBtu = Expression(
        m.FUEL_PRJ_OPR_TMPS,
        rule=total_fuel_burn_rule
    )
Example #6
0
def add_model_components(m, d, scenario_directory, subproblem, stage):
    """
    The following Pyomo model components are defined in this module:

    +-------------------------------------------------------------------------+
    | Sets                                                                    |
    +=========================================================================+
    | | :code:`GEN_HYDRO`                                                     |
    |                                                                         |
    | The set of generators of the :code:`gen_hydro` operational type.        |
    +-------------------------------------------------------------------------+
    | | :code:`GEN_HYDRO_OPR_HRZS`                                            |
    |                                                                         |
    | Two-dimensional set with generators of the :code:`gen_hydro`            |
    | operational type and their operational horizons.                        |
    +-------------------------------------------------------------------------+
    | | :code:`GEN_HYDRO_OPR_TMPS`                                            |
    |                                                                         |
    | Two-dimensional set with generators of the :code:`gen_hydro`            |
    | operational type and their operational timepoints.                      |
    +-------------------------------------------------------------------------+
    | | :code:`GEN_HYDRO_LINKED_TMPS`                                         |
    |                                                                         |
    | Two-dimensional set with generators of the :code:`gen_hydro`            |
    | operational type and their linked timepoints.                           |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Required Input Params                                                   |
    +=========================================================================+
    | | :code:`gen_hydro_max_power_fraction`                                  |
    | | *Defined over*: :code:`GEN_HYDRO_OPR_HRZS`                            |
    | | *Within*: :code:`Reals`                                               |
    |                                                                         |
    | The project's maximum power output in each operational horizon as a     |
    | fraction of its available capacity.                                     |
    +-------------------------------------------------------------------------+
    | | :code:`gen_hydro_min_power_fraction`                                  |
    | | *Defined over*: :code:`GEN_HYDRO_OPR_HRZS`                            |
    | | *Within*: :code:`Reals`                                               |
    |                                                                         |
    | The project's minimum power output in each operational horizon as a     |
    | fraction of its available capacity.                                     |
    +-------------------------------------------------------------------------+
    | | :code:`gen_hydro_average_power_fraction`                              |
    | | *Defined over*: :code:`GEN_HYDRO_OPR_HRZS`                            |
    | | *Within*: :code:`Reals`                                               |
    |                                                                         |
    | The project's avarage power output in each operational horizon as a     |
    | fraction of its available capacity. This can be interpreted as the      |
    | project's average capacity factor or plant load factor.                 |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Optional Input Params                                                   |
    +=========================================================================+
    | | :code:`gen_hydro_ramp_up_when_on_rate`                                |
    | | *Defined over*: :code:`GEN_HYDRO`                                     |
    | | *Within*: :code:`PercentFraction`                                     |
    | | *Default*: :code:`1`                                                  |
    |                                                                         |
    | The project's upward ramp rate limit during operations, defined as a    |
    | fraction of its capacity per minute.                                    |
    +-------------------------------------------------------------------------+
    | | :code:`gen_hydro_ramp_down_when_on_rate`                              |
    | | *Defined over*: :code:`GEN_HYDRO`                                     |
    | | *Within*: :code:`PercentFraction`                                     |
    | | *Default*: :code:`1`                                                  |
    |                                                                         |
    | The project's downward ramp rate limit during operations, defined as a  |
    | fraction of its capacity per minute.                                    |
    +-------------------------------------------------------------------------+
    | | :code:`gen_hydro_aux_consumption_frac_capacity`                       |
    | | *Defined over*: :code:`GEN_HYDRO`                                     |
    | | *Within*: :code:`PercentFraction`                                     |
    | | *Default*: :code:`0`                                                  |
    |                                                                         |
    | Auxiliary consumption as a fraction of capacity. This would be          |
    | incurred in all timepoints when capacity is available.                  |
    +-------------------------------------------------------------------------+
    | | :code:`gen_hydro_aux_consumption_frac_power`                          |
    | | *Defined over*: :code:`GEN_HYDRO`                                     |
    | | *Within*: :code:`PercentFraction`                                     |
    | | *Default*: :code:`0`                                                  |
    |                                                                         |
    | Auxiliary consumption as a fraction of gross power output.              |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Linked Input Params                                                     |
    +=========================================================================+
    | | :code:`gen_hydro_linked_power`                                        |
    | | *Defined over*: :code:`GEN_HYDRO_LINKED_TMPS`                         |
    | | *Within*: :code:`Reals`                                               |
    |                                                                         |
    | The project's power provision in the linked timepoints.                 |
    +-------------------------------------------------------------------------+
    | | :code:`gen_hydro_linked_curtailment`                                  |
    | | *Defined over*: :code:`GEN_HYDRO_LINKED_TMPS`                         |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The project's curtailment in the linked timepoints.                     |
    +-------------------------------------------------------------------------+
    | | :code:`gen_hydro_linked_upwards_reserves`                             |
    | | *Defined over*: :code:`GEN_HYDRO_LINKED_TMPS`                         |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The project's upward reserve provision in the linked timepoints.        |
    +-------------------------------------------------------------------------+
    | | :code:`gen_hydro_linked_downwards_reserves`                           |
    | | *Defined over*: :code:`GEN_HYDRO_LINKED_TMPS`                         |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The project's downward reserve provision in the linked timepoints.      |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Variables                                                               |
    +=========================================================================+
    | | :code:`GenHydro_Gross_Power_MW`                                       |
    | | *Defined over*: :code:`GEN_HYDRO_OPR_TMPS`                            |
    | | *Within*: :code:`Reals`                                               |
    |                                                                         |
    | Gross power in MW from this project in each timepoint in which the      |
    | project is operational (capacity exists and the project is available).  |
    | We'll subtract curtailment and auxiliary consumption from this for      |
    | load balance purposes.                                                  |
    +-------------------------------------------------------------------------+
    | | :code:`GenHydro_Curtail_MW`                                           |
    | | *Defined over*: :code:`GEN_HYDRO_OPR_TMPS`                            |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | Curtailment in MW from this project in each timepoint in which the      |
    | project is operational (capacity exists and the project is available).  |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Expressions                                                             |
    +=========================================================================+
    | | :code:`GenHydro_Auxiliary_Consumption_MW`                             |
    | | *Defined over*: :code:`GEN_HYDRO_OPR_TMPS`                            |
    |                                                                         |
    | The project's auxiliary consumption (power consumed on-site and not     |
    | sent to the grid) in each timepoint.                                    |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Constraints                                                             |
    +=========================================================================+
    | Power                                                                   |
    +-------------------------------------------------------------------------+
    | | :code:`GenHydro_Max_Power_Constraint`                                 |
    | | *Defined over*: :code:`GEN_HYDRO_OPR_HRZS`                            |
    |                                                                         |
    | Limits the power plus upward reserves based on the                      |
    | :code:`gen_hydro_max_power_fraction` and the available capacity.        |
    +-------------------------------------------------------------------------+
    | | :code:`GenHydro_Min_Power_Constraint`                                 |
    | | *Defined over*: :code:`GEN_HYDRO_OPR_HRZS`                            |
    |                                                                         |
    | Power provision minus downward reserves should exceed a certain level   |
    | based on the :code:`gen_hydro_min_power_fraction` and the available     |
    | capacity.                                                               |
    +-------------------------------------------------------------------------+
    | | :code:`GenHydro_Energy_Budget_Constraint`                             |
    | | *Defined over*: :code:`GEN_HYDRO_OPR_HRZS`                            |
    |                                                                         |
    | The project's average capacity factor in each operational horizon,      |
    | including curtailment, should match the specified                       |
    | :code:`gen_hydro_average_power_fraction`.                               |
    +-------------------------------------------------------------------------+
    | Ramps                                                                   |
    +-------------------------------------------------------------------------+
    | | :code:`GenHydro_Ramp_Up_Constraint`                                   |
    | | *Defined over*: :code:`GEN_HYDRO_OPR_TMPS`                            |
    |                                                                         |
    | Limits the allowed project upward ramp based on the                     |
    | :code:`gen_hydro_ramp_up_when_on_rate`.                                 |
    +-------------------------------------------------------------------------+
    | | :code:`GenHydro_Ramp_Down_Constraint`                                 |
    | | *Defined over*: :code:`GEN_HYDRO_OPR_TMPS`                            |
    |                                                                         |
    | Limits the allowed project downward ramp based on the                   |
    | :code:`gen_hydro_ramp_down_when_on_rate`.                               |
    +-------------------------------------------------------------------------+
    | Curtailment                                                             |
    +-------------------------------------------------------------------------+
    | | :code:`GenHydro_Max_Curtailment_Constraint`                           |
    | | *Defined over*: :code:`GEN_HYDRO_OPR_TMPS`                            |
    |                                                                         |
    | Limits the allowed curtailment to the available power.                  |
    +-------------------------------------------------------------------------+

    """
    # Sets
    ###########################################################################

    m.GEN_HYDRO = Set(
        within=m.PROJECTS,
        initialize=lambda mod: subset_init_by_param_value(
            mod, "PROJECTS", "operational_type", "gen_hydro"),
    )

    m.GEN_HYDRO_OPR_HRZS = Set(dimen=2)

    m.GEN_HYDRO_OPR_TMPS = Set(
        dimen=2,
        within=m.PRJ_OPR_TMPS,
        initialize=lambda mod: set(
            (g, tmp) for (g, tmp) in mod.PRJ_OPR_TMPS if g in mod.GEN_HYDRO),
    )

    m.GEN_HYDRO_LINKED_TMPS = Set(dimen=2)

    # Required Params
    ###########################################################################

    m.gen_hydro_max_power_fraction = Param(m.GEN_HYDRO_OPR_HRZS, within=Reals)

    m.gen_hydro_min_power_fraction = Param(m.GEN_HYDRO_OPR_HRZS, within=Reals)

    m.gen_hydro_average_power_fraction = Param(m.GEN_HYDRO_OPR_HRZS,
                                               within=Reals)

    # Optional Params
    ###########################################################################

    m.gen_hydro_ramp_up_when_on_rate = Param(m.GEN_HYDRO,
                                             within=PercentFraction,
                                             default=1)

    m.gen_hydro_ramp_down_when_on_rate = Param(m.GEN_HYDRO,
                                               within=PercentFraction,
                                               default=1)

    m.gen_hydro_aux_consumption_frac_capacity = Param(m.GEN_HYDRO,
                                                      within=PercentFraction,
                                                      default=0)

    m.gen_hydro_aux_consumption_frac_power = Param(m.GEN_HYDRO,
                                                   within=PercentFraction,
                                                   default=0)

    # Linked Params
    ###########################################################################

    m.gen_hydro_linked_power = Param(m.GEN_HYDRO_LINKED_TMPS, within=Reals)

    m.gen_hydro_linked_curtailment = Param(m.GEN_HYDRO_LINKED_TMPS,
                                           within=NonNegativeReals)

    m.gen_hydro_linked_upwards_reserves = Param(m.GEN_HYDRO_LINKED_TMPS,
                                                within=NonNegativeReals)

    m.gen_hydro_linked_downwards_reserves = Param(m.GEN_HYDRO_LINKED_TMPS,
                                                  within=NonNegativeReals)

    # Variables
    ###########################################################################

    m.GenHydro_Gross_Power_MW = Var(m.GEN_HYDRO_OPR_TMPS, within=Reals)

    m.GenHydro_Curtail_MW = Var(m.GEN_HYDRO_OPR_TMPS, within=NonNegativeReals)

    # Expressions
    ###########################################################################

    def upwards_reserve_rule(mod, g, tmp):
        return sum(
            getattr(mod, c)[g, tmp] for c in getattr(d, headroom_variables)[g])

    m.GenHydro_Upwards_Reserves_MW = Expression(m.GEN_HYDRO_OPR_TMPS,
                                                rule=upwards_reserve_rule)

    def downwards_reserve_rule(mod, g, tmp):
        return sum(
            getattr(mod, c)[g, tmp] for c in getattr(d, footroom_variables)[g])

    m.GenHydro_Downwards_Reserves_MW = Expression(m.GEN_HYDRO_OPR_TMPS,
                                                  rule=downwards_reserve_rule)

    def auxiliary_consumption_rule(mod, g, tmp):
        """
        **Expression Name**: GenHydro_Auxiliary_Consumption_MW
        **Defined Over**: GEN_HYDRO_OPR_TMPS
        """
        return (mod.Capacity_MW[g, mod.period[tmp]] *
                mod.Availability_Derate[g, tmp] *
                mod.gen_hydro_aux_consumption_frac_capacity[g] +
                mod.GenHydro_Gross_Power_MW[g, tmp] *
                mod.gen_hydro_aux_consumption_frac_power[g])

    m.GenHydro_Auxiliary_Consumption_MW = Expression(
        m.GEN_HYDRO_OPR_TMPS, rule=auxiliary_consumption_rule)

    # Constraints
    ###########################################################################

    m.GenHydro_Max_Power_Constraint = Constraint(m.GEN_HYDRO_OPR_TMPS,
                                                 rule=max_power_rule)

    m.GenHydro_Min_Power_Constraint = Constraint(m.GEN_HYDRO_OPR_TMPS,
                                                 rule=min_power_rule)

    m.GenHydro_Energy_Budget_Constraint = Constraint(m.GEN_HYDRO_OPR_HRZS,
                                                     rule=energy_budget_rule)

    m.GenHydro_Ramp_Up_Constraint = Constraint(m.GEN_HYDRO_OPR_TMPS,
                                               rule=ramp_up_rule)

    m.GenHydro_Ramp_Down_Constraint = Constraint(m.GEN_HYDRO_OPR_TMPS,
                                                 rule=ramp_down_rule)

    m.GenHydro_Max_Curtailment_Constraint = Constraint(
        m.GEN_HYDRO_OPR_TMPS, rule=max_curtailment_rule)
Example #7
0
def add_model_components(m, d, scenario_directory, subproblem, stage):
    """
    The following Pyomo model components are defined in this module:

    +-------------------------------------------------------------------------+
    | Sets                                                                    |
    +=========================================================================+
    | | :code:`GEN_VAR`                                                       |
    |                                                                         |
    | The set of generators of the :code:`gen_var` operational type.          |
    +-------------------------------------------------------------------------+
    | | :code:`GEN_VAR_OPR_TMPS`                                              |
    |                                                                         |
    | Two-dimensional set with generators of the :code:`gen_var`              |
    | operational type and their operational timepoints.                      |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Required Input Params                                                   |
    +=========================================================================+
    | | :code:`gen_var_cap_factor`                                            |
    | | *Defined over*: :code:`GEN_VAR`                                       |
    | | *Within*: :code:`Reals`                                               |
    |                                                                         |
    | The project's power output in each operational timepoint as a fraction  |
    | of its available capacity (i.e. the capacity factor).                   |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Variables                                                               |
    +=========================================================================+
    | | :code:`GenVar_Provide_Power_MW`                                       |
    | | *Defined over*: :code:`GEN_VAR_OPR_TMPS`                              |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | Power provision in MW from this project in each timepoint in which the  |
    | project is operational (capacity exists and the project is available).  |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Expressions                                                             |
    +=========================================================================+
    | | :code:`GenVar_Subhourly_Curtailment_MW`                               |
    | | *Defined over*: :code:`GEN_VAR_OPR_TMPS`                              |
    |                                                                         |
    | Sub-hourly curtailment (in MW) from providing downward reserves.        |
    +-------------------------------------------------------------------------+
    | | :code:`GenVar_Subhourly_Energy_Delivered_MW`                          |
    | | *Defined over*: :code:`GEN_VAR_OPR_TMPS`                              |
    |                                                                         |
    | Sub-hourly energy delivered (in MW) from providing upward reserves.     |
    +-------------------------------------------------------------------------+
    | | :code:`GenVar_Scheduled_Curtailment_MW`                               |
    | | *Defined over*: :code:`GEN_VAR_OPR_TMPS`                              |
    |                                                                         |
    | The available power minus what was actually provided (in MW).           |
    +-------------------------------------------------------------------------+
    | | :code:`GenVar_Total_Curtailment_MW`                                   |
    | | *Defined over*: :code:`GEN_VAR_OPR_TMPS`                              |
    |                                                                         |
    | Scheduled curtailment (in MW) plus an upward adjustment for additional  |
    | curtailment when providing downward reserves, and a downward adjustment |
    | adjustment for a reduction in curtailment when providing upward         |
    | reserves, to account for sub-hourly dispatch when providing reserves.   |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Constraints                                                             |
    +=========================================================================+
    | Power                                                                   |
    +-------------------------------------------------------------------------+
    | | :code:`GenVar_Max_Power_Constraint`                                   |
    | | *Defined over*: :code:`GEN_VAR_OPR_TMPS`                              |
    |                                                                         |
    | Limits the power plus upward reserves in each timepoint based on the    |
    | :code:`gen_var_cap_factor` and the available capacity.                  |
    +-------------------------------------------------------------------------+
    | | :code:`GenVar_Min_Power_Constraint`                                   |
    | | *Defined over*: :code:`GEN_VAR_OPR_TMPS`                              |
    |                                                                         |
    | Power provision minus downward reserves should exceed zero.             |
    +-------------------------------------------------------------------------+

    """

    # Sets
    ###########################################################################
    m.GEN_VAR = Set(
        within=m.PROJECTS,
        initialize=lambda mod: subset_init_by_param_value(
            mod, "PROJECTS", "operational_type", "gen_var"
        ),
    )

    m.GEN_VAR_OPR_TMPS = Set(
        dimen=2,
        within=m.PRJ_OPR_TMPS,
        initialize=lambda mod: list(
            set((g, tmp) for (g, tmp) in mod.PRJ_OPR_TMPS if g in mod.GEN_VAR)
        ),
    )

    # Required Params
    ###########################################################################

    m.gen_var_cap_factor = Param(m.GEN_VAR_OPR_TMPS, within=Reals)

    # Variables
    ###########################################################################

    m.GenVar_Provide_Power_MW = Var(m.GEN_VAR_OPR_TMPS, within=NonNegativeReals)

    # Expressions
    ###########################################################################

    def upwards_reserve_rule(mod, g, tmp):
        """
        Gather all headroom variables, and de-rate the total reserves offered
        to account for the fact that gen_var output is uncertain.
        """
        return sum(
            getattr(mod, c)[g, tmp]
            / getattr(mod, getattr(d, reserve_variable_derate_params)[c])[g]
            for c in getattr(d, headroom_variables)[g]
        )

    m.GenVar_Upwards_Reserves_MW = Expression(
        m.GEN_VAR_OPR_TMPS, rule=upwards_reserve_rule
    )

    def downwards_reserve_rule(mod, g, tmp):
        """
        Gather all footroom variables, and de-rate the total reserves offered
        to account for the fact that gen_var output is uncertain.
        """
        return sum(
            getattr(mod, c)[g, tmp]
            / getattr(mod, getattr(d, reserve_variable_derate_params)[c])[g]
            for c in getattr(d, footroom_variables)[g]
        )

    m.GenVar_Downwards_Reserves_MW = Expression(
        m.GEN_VAR_OPR_TMPS, rule=downwards_reserve_rule
    )

    def subhourly_curtailment_expression_rule(mod, g, tmp):
        """
        Sub-hourly curtailment from providing downward reserves.
        """
        return footroom_subhourly_energy_adjustment_rule(d, mod, g, tmp)

    m.GenVar_Subhourly_Curtailment_MW = Expression(
        m.GEN_VAR_OPR_TMPS, rule=subhourly_curtailment_expression_rule
    )

    def subhourly_delivered_energy_expression_rule(mod, g, tmp):
        """
        Sub-hourly energy delivered from providing upward reserves.
        """
        return headroom_subhourly_energy_adjustment_rule(d, mod, g, tmp)

    m.GenVar_Subhourly_Energy_Delivered_MW = Expression(
        m.GEN_VAR_OPR_TMPS, rule=subhourly_delivered_energy_expression_rule
    )

    m.GenVar_Scheduled_Curtailment_MW = Expression(
        m.GEN_VAR_OPR_TMPS, rule=scheduled_curtailment_expression_rule
    )

    m.GenVar_Total_Curtailment_MW = Expression(
        m.GEN_VAR_OPR_TMPS, rule=total_curtailment_expression_rule
    )

    # Constraints
    ###########################################################################

    m.GenVar_Max_Power_Constraint = Constraint(m.GEN_VAR_OPR_TMPS, rule=max_power_rule)

    m.GenVar_Min_Power_Constraint = Constraint(m.GEN_VAR_OPR_TMPS, rule=min_power_rule)
Example #8
0
def generic_add_model_components(
    m,
    d,
    reserve_zone_set,
    reserve_violation_variable,
    reserve_violation_expression,
    reserve_violation_allowed_param,
    reserve_requirement_expression,
    total_reserve_provision_expression,
    meet_reserve_constraint,
):
    """
    Ensure reserves are balanced
    :param m:
    :param d:
    :param reserve_zone_set:
    :param reserve_violation_variable:
    :param reserve_violation_expression:
    :param reserve_violation_allowed_param:
    :param reserve_requirement_expression:
    :param total_reserve_provision_expression:
    :param meet_reserve_constraint:
    :return:
    """

    # Penalty for violation
    setattr(
        m,
        reserve_violation_variable,
        Var(getattr(m, reserve_zone_set), m.TMPS, within=NonNegativeReals),
    )

    def violation_expression_rule(mod, ba, tmp):
        """

        :param mod:
        :param ba:
        :param tmp:
        :return:
        """
        return (
            getattr(mod, reserve_violation_allowed_param)[ba]
            * getattr(mod, reserve_violation_variable)[ba, tmp]
        )

    setattr(
        m,
        reserve_violation_expression,
        Expression(
            getattr(m, reserve_zone_set), m.TMPS, rule=violation_expression_rule
        ),
    )

    # Reserve constraints
    def meet_reserve_rule(mod, ba, tmp):
        return (
            getattr(mod, total_reserve_provision_expression)[ba, tmp]
            + getattr(mod, reserve_violation_expression)[ba, tmp]
            == getattr(mod, reserve_requirement_expression)[ba, tmp]
        )

    setattr(
        m,
        meet_reserve_constraint,
        Constraint(getattr(m, reserve_zone_set), m.TMPS, rule=meet_reserve_rule),
    )
Example #9
0
    def build(self):
        """Callable method for Block construction."""
        super(IdealStateBlockData, self).build()

        # Add state variables
        self.flow_mol_phase_comp = Var(
            self._params.phase_list,
            self._params.component_list,
            initialize=0.5,
            bounds=(1e-8, 100),
            doc='Phase-component molar flow rates [mol/s]')

        self.pressure = Var(initialize=101325,
                            bounds=(101325, 400000),
                            domain=NonNegativeReals,
                            doc='State pressure [Pa]')
        self.temperature = Var(initialize=298.15,
                               bounds=(298.15, 1000),
                               domain=NonNegativeReals,
                               doc='State temperature [K]')

        # Add supporting variables
        def flow_mol_phase(b, p):
            return sum(b.flow_mol_phase_comp[p, j]
                       for j in b._params.component_list)

        self.flow_mol_phase = Expression(self._params.phase_list,
                                         rule=flow_mol_phase,
                                         doc='Phase molar flow rates [mol/s]')

        def flow_mol(b):
            return sum(b.flow_mol_phase_comp[p, j]
                       for j in b._params.component_list
                       for p in b._params.phase_list)

        self.flow_mol = Expression(rule=flow_mol,
                                   doc='Total molar flowrate [mol/s]')

        def mole_frac_phase_comp(b, p, j):
            return b.flow_mol_phase_comp[p, j] / b.flow_mol_phase[p]

        self.mole_frac_phase_comp = Expression(self._params.phase_list,
                                               self._params.component_list,
                                               rule=mole_frac_phase_comp,
                                               doc='Phase mole fractions [-]')

        def mole_frac_comp(b, j):
            return (sum(b.flow_mol_phase_comp[p, j]
                        for p in b._params.phase_list) / b.flow_mol)

        self.mole_frac_comp = Expression(self._params.component_list,
                                         rule=mole_frac_comp,
                                         doc='Mixture mole fractions [-]')

        # Reaction Stoichiometry
        add_object_reference(self, "phase_equilibrium_list_ref",
                             self._params.phase_equilibrium_list)

        if (self.config.has_phase_equilibrium
                and self.config.defined_state is False):
            # Definition of equilibrium temperature for smooth VLE
            self._teq = Var(
                initialize=self.temperature.value,
                doc='Temperature for calculating phase equilibrium')
            self._t1 = Var(initialize=self.temperature.value,
                           doc='Intermediate temperature for calculating Teq')

            self.eps_1 = Param(default=0.01,
                               mutable=True,
                               doc='Smoothing parameter for Teq')
            self.eps_2 = Param(default=0.0005,
                               mutable=True,
                               doc='Smoothing parameter for Teq')

            # PSE paper Eqn 13
            def rule_t1(b):
                return b._t1 == 0.5 * (
                    b.temperature + b.temperature_bubble +
                    sqrt((b.temperature - b.temperature_bubble)**2 +
                         b.eps_1**2))

            self._t1_constraint = Constraint(rule=rule_t1)

            # PSE paper Eqn 14
            # TODO : Add option for supercritical extension
            def rule_teq(b):
                return b._teq == 0.5 * (b._t1 + b.temperature_dew - sqrt(
                    (b._t1 - b.temperature_dew)**2 + b.eps_2**2))

            self._teq_constraint = Constraint(rule=rule_teq)

            def rule_tr_eq(b, i):
                return b._teq / b._params.temperature_crit[i]

            self._tr_eq = Expression(self._params.component_list,
                                     rule=rule_tr_eq,
                                     doc='Component reduced temperatures [-]')

            def rule_equilibrium(b, i):
                return b.fug_vap[i] == b.fug_liq[i]

            self.equilibrium_constraint = Constraint(
                self._params.component_list, rule=rule_equilibrium)
Example #10
0
def add_model_components(m, d, scenario_directory, subproblem, stage):
    """
    The following Pyomo model components are defined in this module:

    +-------------------------------------------------------------------------+
    | Sets                                                                    |
    +=========================================================================+
    | | :code:`STOR`                                                          |
    |                                                                         |
    | The set of projects of the :code:`stor` operational type.               |
    +-------------------------------------------------------------------------+
    | | :code:`STOR_OPR_TMPS`                                                 |
    |                                                                         |
    | Two-dimensional set with projects of the :code:`stor`                   |
    | operational type and their operational timepoints.                      |
    +-------------------------------------------------------------------------+
    | | :code:`STOR_LINKED_TMPS`                                              |
    |                                                                         |
    | Two-dimensional set with generators of the :code:`stor`                 |
    | operational type and their linked timepoints.                           |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Required Input Params                                                   |
    +=========================================================================+
    | | :code:`stor_charging_efficiency`                                      |
    | | *Defined over*: :code:`STOR`                                          |
    | | *Within*: :code:`PercentFraction`                                     |
    |                                                                         |
    | The storage project's charging efficiency (1 = 100% efficient).         |
    +-------------------------------------------------------------------------+
    | | :code:`stor_discharging_efficiency`                                   |
    | | *Defined over*: :code:`STOR`                                          |
    | | *Within*: :code:`PercentFraction`                                     |
    |                                                                         |
    | The storage project's discharging efficiency (1 = 100% efficient).      |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Optional Input Params                                                   |
    +=========================================================================+
    | | :code:`stor_losses_factor_in_rps`                                     |
    | | *Within*: :code:`PercentFraction`                                     |
    | | *Default*: :code:`1`                                                  |
    |                                                                         |
    | The fraction of storage losses that count against the RPS target.       |
    +-------------------------------------------------------------------------+
    | | :code:`stor_charging_capacity_multiplier`                             |
    | | *Defined over*: :code:`STOR`                                          |
    | | *Within*: :code:`NonNegativeReals`                                    |
    | | *Default*: :code:`1.0`                                                |
    |                                                                         |
    | The storage project's charging capacity multiplier to be used if the    |
    | charging capacity is different from the nameplate capacity.             |
    +-------------------------------------------------------------------------+
    | | :code:`stor_discharging_capacity_multiplier`                          |
    | | *Defined over*: :code:`STOR`                                          |
    | | *Within*: :code:`NonNegativeReals`                                    |
    | | *Default*: :code:`1.0`                                                |
    |                                                                         |
    | The storage project's discharging capacity multiplier to be used if the |
    | discharging capacity is different from the nameplate capacity.          |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Linked Input Params                                                     |
    +=========================================================================+
    | | :code:`stor_linked_starting_energy_in_storage`                        |
    | | *Defined over*: :code:`STOR_LINKED_TMPS`                              |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The project's starting energy in storage in the linked timepoints.      |
    +-------------------------------------------------------------------------+
    | | :code:`stor_linked_discharge`                                         |
    | | *Defined over*: :code:`STOR_LINKED_TMPS`                              |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The project's dicharging in the linked timepoints.                      |
    +-------------------------------------------------------------------------+
    | | :code:`stor_linked_charge`                                            |
    | | *Defined over*: :code:`STOR_LINKED_TMPS`                              |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The project's charging in the linked timepoints.                        |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Variables                                                               |
    +=========================================================================+
    | | :code:`Stor_Charge_MW`                                                |
    | | *Defined over*: :code:`STOR_OPR_TMPS`                                 |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | Charging power in MW from this project in each timepoint in which the   |
    | project is operational (capacity exists and the project is available).  |
    +-------------------------------------------------------------------------+
    | | :code:`Stor_Discharge_MW`                                             |
    | | *Defined over*: :code:`STOR_OPR_TMPS`                                 |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | Discharging power in MW from this project in each timepoint in which the|
    |  project is operational (capacity exists and the project is available). |
    +-------------------------------------------------------------------------+
    | | :code:`Stor_Starting_Energy_in_Storage_MWh`                           |
    | | *Defined over*: :code:`STOR_OPR_TMPS`                                 |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The state of charge of the storage project at the start of each         |
    | timepoint, in MWh of energy stored.                                     |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Constraints                                                             |
    +=========================================================================+
    | Power and Stage of Charge                                               |
    +-------------------------------------------------------------------------+
    | | :code:`Stor_Max_Charge_Constraint`                                    |
    | | *Defined over*: :code:`STOR_OPR_TMPS`                                 |
    |                                                                         |
    | Limits the project's charging power to the available capacity.          |
    +-------------------------------------------------------------------------+
    | | :code:`Stor_Max_Discharge_Constraint`                                 |
    | | *Defined over*: :code:`STOR_OPR_TMPS`                                 |
    |                                                                         |
    | Limits the project's discharging power to the available capacity.       |
    +-------------------------------------------------------------------------+
    | | :code:`Stor_Energy_Tracking_Constraint`                               |
    | | *Defined over*: :code:`STOR_OPR_TMPS`                                 |
    |                                                                         |
    | Tracks the amount of energy stored in each timepoint based on the       |
    | previous timepoint's energy stored and the charge and discharge         |
    | decisions.                                                              |
    +-------------------------------------------------------------------------+
    | | :code:`Stor_Max_Energy_in_Storage_Constraint`                         |
    | | *Defined over*: :code:`STOR_OPR_TMPS`                                 |
    |                                                                         |
    | Limits the project's total energy stored to the available energy        |
    | capacity.                                                               |
    +-------------------------------------------------------------------------+
    | Reserves                                                                |
    +-------------------------------------------------------------------------+
    | | :code:`Stor_Max_Headroom_Power_Constraint`                            |
    | | *Defined over*: :code:`STOR_OPR_TMPS`                                 |
    |                                                                         |
    | Limits the project's upward reserves based on available headroom.       |
    | Going from charging to non-charging also counts as headroom, doubling   |
    | the maximum amount of potential headroom.                               |
    +-------------------------------------------------------------------------+
    | | :code:`Stor_Max_Footroom_Power_Constraint`                            |
    | | *Defined over*: :code:`STOR_OPR_TMPS`                                 |
    |                                                                         |
    | Limits the project's downward reserves based on available footroom.     |
    | Going from non-charging to charging also counts as footroom, doubling   |
    | the maximum amount of potential footroom.                               |
    +-------------------------------------------------------------------------+
    | | :code:`Stor_Max_Headroom_Energy_Constraint`                           |
    | | *Defined over*: :code:`STOR_OPR_TMPS`                                 |
    |                                                                         |
    | Can't provide more upward reserves (times sustained duration required)  |
    |than available energy in storage in that timepoint.                      |
    +-------------------------------------------------------------------------+
    | | :code:`Stor_Max_Footroom_Energy_Constraint`                           |
    | | *Defined over*: :code:`STOR_OPR_TMPS`                                 |
    |                                                                         |
    | Can't provide more downard reserves (times sustained duration required) |
    | than available capacity to store energy in that timepoint.              |
    +-------------------------------------------------------------------------+



    """

    # Sets
    ###########################################################################

    m.STOR = Set(
        within=m.PROJECTS,
        initialize=lambda mod: subset_init_by_param_value(
            mod, "PROJECTS", "operational_type", "stor"
        )
    )

    m.STOR_OPR_TMPS = Set(
        dimen=2, within=m.PRJ_OPR_TMPS,
        initialize=lambda mod: list(
            set((g, tmp) for (g, tmp) in mod.PRJ_OPR_TMPS
                if g in mod.STOR)
        )
    )

    m.STOR_LINKED_TMPS = Set(dimen=2)

    # Required Params
    ###########################################################################

    m.stor_charging_efficiency = Param(
        m.STOR, within=PercentFraction
    )

    m.stor_discharging_efficiency = Param(
        m.STOR, within=PercentFraction
    )

    m.stor_charging_capacity_multiplier = Param(
        m.STOR, within=NonNegativeReals, default=1.0
    )

    m.stor_discharging_capacity_multiplier = Param(
        m.STOR, within=NonNegativeReals, default=1.0
    )

    # Optional Params
    ###########################################################################

    m.stor_losses_factor_in_rps = Param(default=1)

    # Linked Params
    ###########################################################################

    m.stor_linked_starting_energy_in_storage = Param(
        m.STOR_LINKED_TMPS,
        within=NonNegativeReals
    )

    m.stor_linked_discharge = Param(
        m.STOR_LINKED_TMPS,
        within=NonNegativeReals
    )

    m.stor_linked_charge = Param(
        m.STOR_LINKED_TMPS,
        within=NonNegativeReals
    )

    # Variables
    ###########################################################################

    m.Stor_Charge_MW = Var(
        m.STOR_OPR_TMPS,
        within=NonNegativeReals
    )

    m.Stor_Discharge_MW = Var(
        m.STOR_OPR_TMPS,
        within=NonNegativeReals
    )

    m.Stor_Starting_Energy_in_Storage_MWh = Var(
        m.STOR_OPR_TMPS,
        within=NonNegativeReals
    )

    # Expressions
    ###########################################################################

    def upward_reserve_rule(mod, g, tmp):
        return sum(getattr(mod, c)[g, tmp]
                   for c in getattr(d, headroom_variables)[g])
    m.Stor_Upward_Reserves_MW = Expression(
        m.STOR_OPR_TMPS,
        rule=upward_reserve_rule
    )

    def downward_reserve_rule(mod, g, tmp):
        return sum(getattr(mod, c)[g, tmp]
                   for c in getattr(d, footroom_variables)[g])
    m.Stor_Downward_Reserves_MW = Expression(
        m.STOR_OPR_TMPS,
        rule=downward_reserve_rule
    )

    # Constraints
    ###########################################################################

    # Power and State of Charge
    m.Stor_Max_Charge_Constraint = Constraint(
        m.STOR_OPR_TMPS,
        rule=max_charge_rule
    )

    m.Stor_Max_Discharge_Constraint = Constraint(
        m.STOR_OPR_TMPS,
        rule=max_discharge_rule
    )

    m.Stor_Energy_Tracking_Constraint = Constraint(
        m.STOR_OPR_TMPS,
        rule=energy_tracking_rule
    )

    m.Stor_Max_Energy_in_Storage_Constraint = Constraint(
        m.STOR_OPR_TMPS,
        rule=max_energy_in_storage_rule
    )

    # Reserves
    m.Stor_Max_Headroom_Power_Constraint = Constraint(
        m.STOR_OPR_TMPS,
        rule=max_headroom_power_rule
    )

    m.Stor_Max_Footroom_Power_Constraint = Constraint(
        m.STOR_OPR_TMPS,
        rule=max_footroom_power_rule
    )

    m.Stor_Max_Headroom_Energy_Constraint = Constraint(
        m.STOR_OPR_TMPS,
        rule=max_headroom_energy_rule
    )

    m.Stor_Max_Footroom_Energy_Constraint = Constraint(
        m.STOR_OPR_TMPS,
        rule=max_footroom_energy_rule
    )
def add_model_components(m, d, scenario_directory, subproblem, stage):
    """

    :param m:
    :param d:
    :return:
    """

    m.ENERGY_TARGET_ZONE_BLN_TYPE_HRZS_WITH_ENERGY_TARGET = Set(
        dimen=3, within=m.ENERGY_TARGET_ZONES * m.BLN_TYPE_HRZS)

    # RPS target specified in energy terms
    m.horizon_energy_target_mwh = Param(
        m.ENERGY_TARGET_ZONE_BLN_TYPE_HRZS_WITH_ENERGY_TARGET,
        within=NonNegativeReals,
        default=0,
    )

    # RPS target specified in 'percent of load' terms
    m.horizon_energy_target_fraction = Param(
        m.ENERGY_TARGET_ZONE_BLN_TYPE_HRZS_WITH_ENERGY_TARGET,
        within=PercentFraction,
        default=0,
    )

    # Load zones included in RPS percentage target
    m.HORIZON_ENERGY_TARGET_ZONE_LOAD_ZONES = Set(
        dimen=2, within=m.ENERGY_TARGET_ZONES * m.LOAD_ZONES)

    def energy_target_rule(mod, energy_target_zone, bt, h):
        """
        The RPS target consists of two additive components: an energy term
        and a 'percent of load x load' term, where a mapping between the RPS
        zone and the load zones whose load to consider must be specified.
        Either the energy target or the percent target can be omitted (they
        default to 0). If a mapping is not specified, the
        'percent of load x load' is 0.
        """
        # If we have a map of RPS zones to load zones, apply the percentage
        # target; if no map provided, the fraction_target is 0
        if mod.HORIZON_ENERGY_TARGET_ZONE_LOAD_ZONES:
            total_bt_horizon_static_load = sum(
                mod.static_load_mw[lz, tmp] * mod.hrs_in_tmp[tmp] *
                mod.tmp_weight[tmp] for (
                    _energy_target_zone,
                    lz,
                ) in mod.HORIZON_ENERGY_TARGET_ZONE_LOAD_ZONES
                if _energy_target_zone == energy_target_zone
                for tmp in mod.TMPS if tmp in mod.TMPS_BY_BLN_TYPE_HRZ[bt, h])
            fraction_target = (
                mod.horizon_energy_target_fraction[energy_target_zone, bt, h] *
                total_bt_horizon_static_load)
        else:
            fraction_target = 0

        return (mod.horizon_energy_target_mwh[energy_target_zone, bt, h] +
                fraction_target)

    m.Horizon_Energy_Target = Expression(
        m.ENERGY_TARGET_ZONE_BLN_TYPE_HRZS_WITH_ENERGY_TARGET,
        rule=energy_target_rule)
Example #12
0
    def _make_splits_reboiler(self):
        # Get dict of Port members and names
        member_list = self.control_volume.\
            properties_out[0].define_port_members()

        # Create references and populate the reflux, distillate ports
        for k in member_list:
            # Create references and populate the intensive variables
            if "flow" not in k and "frac" not in k and "enth" not in k:
                if not member_list[k].is_indexed():
                    var = self.control_volume.properties_out[:].\
                        component(member_list[k].local_name)
                else:
                    var = self.control_volume.properties_out[:].\
                        component(member_list[k].local_name)[...]

                # add the reference and variable name to the reflux port
                self.bottoms.add(Reference(var), k)

                # add the reference and variable name to the
                # vapor outlet port
                self.vapor_reboil.add(Reference(var), k)

            elif "frac" in k and ("mole" in k or "mass" in k):

                # Mole/mass frac is typically indexed
                index_set = member_list[k].index_set()

                # if state var is not mole/mass frac by phase
                if "phase" not in k:
                    # Assuming the state block has the var
                    # "mole_frac_phase_comp". Valid if VLE is supported
                    # Create a string "mole_frac_phase_comp" or
                    # "mass_frac_phase_comp". Cannot directly append phase to
                    # k as the naming convention is phase followed by comp

                    str_split = k.split('_')
                    local_name = '_'.join(str_split[0:2]) + \
                        "_phase" + "_" + str_split[2]

                    # Rule for liquid fraction
                    def rule_liq_frac(self, t, i):
                        return self.control_volume.properties_out[t].\
                            component(local_name)["Liq", i]
                    self.e_liq_frac = Expression(
                        self.flowsheet().time, index_set,
                        rule=rule_liq_frac)

                    # Rule for vapor fraction
                    def rule_vap_frac(self, t, i):
                        return self.control_volume.properties_out[t].\
                            component(local_name)["Vap", i]
                    self.e_vap_frac = Expression(
                        self.flowsheet().time, index_set,
                        rule=rule_vap_frac)

                    # add the reference and variable name to the
                    # distillate port
                    self.bottoms.add(self.e_liq_frac, k)

                    # add the reference and variable name to the
                    # vapor port
                    self.vapor_reboil.add(self.e_vap_frac, k)

                else:

                    # Assumes mole_frac_phase or mass_frac_phase exist as
                    # state vars in the port and therefore access directly
                    # from the state block.
                    var = self.control_volume.properties_out[:].\
                        component(member_list[k].local_name)[...]

                    # add the reference and variable name to the distillate port
                    self.bottoms.add(Reference(var), k)

                    # add the reference and variable name to the boil up port
                    self.vapor_reboil.add(Reference(var), k)
            elif "flow" in k:
                if "phase" not in k:

                    # Assumes that here the var is total flow or component
                    # flow. However, need to extract the flow by phase from
                    # the state block. Expects to find the var
                    # flow_mol_phase or flow_mass_phase in the state block.

                    # Check if it is not indexed by component list and this
                    # is total flow
                    if not member_list[k].is_indexed():
                        # if state var is not flow_mol/flow_mass
                        # by phase
                        local_name = str(member_list[k].local_name) + \
                            "_phase"

                        # Rule for vap flow
                        def rule_vap_flow(self, t):
                            return self.control_volume.properties_out[t].\
                                component(local_name)["Vap"]
                        self.e_vap_flow = Expression(
                            self.flowsheet().time,
                            rule=rule_vap_flow)

                        # Rule to link the liq flow to the distillate
                        def rule_bottoms_flow(self, t):
                            return self.control_volume.properties_out[t].\
                                component(local_name)["Liq"]
                        self.e_bottoms_flow = Expression(
                            self.flowsheet().time,
                            rule=rule_bottoms_flow)

                    else:
                        # when it is flow comp indexed by component list
                        str_split = \
                            str(member_list[k].local_name).split("_")
                        if len(str_split) == 3 and str_split[-1] == "comp":
                            local_name = str_split[0] + "_" + \
                                str_split[1] + "_phase_" + "comp"

                        # Get the indexing set i.e. component list
                        index_set = member_list[k].index_set()

                        # Rule for vap flow
                        def rule_vap_flow(self, t, i):
                            return self.control_volume.properties_out[t].\
                                component(local_name)["Vap", i]
                        self.e_vap_flow = Expression(
                            self.flowsheet().time, index_set,
                            rule=rule_vap_flow)

                        # Rule to link the liq flow to the distillate
                        def rule_bottoms_flow(self, t, i):
                            return self.control_volume.properties_out[t].\
                                component(local_name)["Liq", i]
                        self.e_bottoms_flow = Expression(
                            self.flowsheet().time, index_set,
                            rule=rule_bottoms_flow)

                    # add the reference and variable name to the
                    # distillate port
                    self.bottoms.add(self.e_bottoms_flow, k)

                    # add the reference and variable name to the
                    # distillate port
                    self.vapor_reboil.add(self.e_vap_flow, k)
            elif "enth" in k:
                if "phase" not in k:
                    # assumes total mixture enthalpy (enth_mol or enth_mass)
                    if not member_list[k].is_indexed():
                        # if state var is not enth_mol/enth_mass
                        # by phase, add _phase string to extract the right
                        # value from the state block
                        local_name = str(member_list[k].local_name) + \
                            "_phase"
                    else:
                        raise PropertyPackageError(
                            "Enthalpy is indexed but the variable "
                            "name does not reflect the presence of an index. "
                            "Please follow the naming convention outlined "
                            "in the documentation for state variables.")

                    # Rule for vap enthalpy. Setting the enthalpy to the
                    # enth_mol_phase['Vap'] value from the state block
                    def rule_vap_enth(self, t):
                        return self.control_volume.properties_out[t].\
                            component(local_name)["Vap"]
                    self.e_vap_enth = Expression(
                        self.flowsheet().time,
                        rule=rule_vap_enth)

                    # Rule to link the liq flow to the distillate.
                    # Setting the enthalpy to the
                    # enth_mol_phase['Liq'] value from the state block
                    def rule_bottoms_enth(self, t):
                        return self.control_volume.properties_out[t].\
                            component(local_name)["Liq"]
                    self.e_bottoms_enth = Expression(
                        self.flowsheet().time,
                        rule=rule_bottoms_enth)

                    # add the reference and variable name to the
                    # distillate port
                    self.bottoms.add(self.e_bottoms_enth, k)

                    # add the reference and variable name to the
                    # distillate port
                    self.vapor_reboil.add(self.e_vap_enth, k)
                elif "phase" in k:
                    # assumes enth_mol_phase or enth_mass_phase.
                    # This is an intensive property, you create a direct
                    # reference irrespective of the reflux, distillate and
                    # vap_outlet

                    # Rule for vap flow
                    if not member_list[k].is_indexed():
                        var = self.control_volume.properties_out[:].\
                            component(member_list[k].local_name)
                    else:
                        var = self.control_volume.properties_out[:].\
                            component(member_list[k].local_name)[...]

                    # add the reference and variable name to the distillate port
                    self.bottoms.add(Reference(var), k)

                    # add the reference and variable name to the
                    # vapor outlet port
                    self.vapor_reboil.add(Reference(var), k)
                else:
                    raise PropertyNotSupportedError(
                        "Unrecognized enthalpy state variable encountered "
                        "while building ports for the reboiler. Only total "
                        "mixture enthalpy or enthalpy by phase are supported.")
    def build(self):
        # Call super.build to setup model
        # This will create the control volumes, ports and basic equations
        super().build()

        # Units will be based on hot side properties
        units_meta = self.config.hot_side.property_package.get_metadata(
        ).get_derived_units

        # ---------------------------------------------------------------------
        # Plate design variables and parameter
        self.number_of_passes = Param(initialize=self.config.passes,
                                      units=pyunits.dimensionless,
                                      domain=PositiveIntegers,
                                      doc="Number of hot/cold fluid passes",
                                      mutable=True)

        # Assuming number of channels is equal in all plates
        self.channels_per_pass = Param(
            initialize=self.config.channels_per_pass,
            units=pyunits.dimensionless,
            domain=PositiveIntegers,
            doc="Number of channels in each pass",
            mutable=True)

        self.number_of_divider_plates = Param(
            initialize=self.config.number_of_divider_plates,
            units=pyunits.dimensionless,
            domain=NonNegativeIntegers,
            doc="Number of divider plates in heat exchanger",
            mutable=True)

        self.plate_length = Var(initialize=1.6925,
                                units=units_meta("length"),
                                domain=PositiveReals,
                                doc="Length of heat exchanger plate")
        self.plate_width = Var(initialize=0.6135,
                               units=units_meta("length"),
                               domain=PositiveReals,
                               doc="Width of heat exchanger plate")
        self.plate_thickness = Var(initialize=0.0006,
                                   units=units_meta("length"),
                                   domain=PositiveReals,
                                   doc="Thickness of heat exchanger plate")
        self.plate_pact_length = Var(initialize=0.381,
                                     units=units_meta("length"),
                                     domain=PositiveReals,
                                     doc="Compressed plate pact length")
        self.port_diameter = Var(initialize=0.2045,
                                 units=units_meta("length"),
                                 domain=PositiveReals,
                                 doc="Port diamter")

        self.plate_therm_cond = Var(
            initialize=16.2,
            units=units_meta("thermal_conductivity"),
            domain=PositiveReals,
            doc="Thermal conductivity heat exchanger plates")

        # Set default value of total heat transfer area
        self.area.set_value(114.3)

        # ---------------------------------------------------------------------
        # Derived geometric quantities
        total_plates = (2 * self.channels_per_pass * self.number_of_passes +
                        1 + self.number_of_divider_plates)
        total_active_plates = (
            2 * self.channels_per_pass * self.number_of_passes -
            (1 + self.number_of_divider_plates))

        self.plate_gap = Expression(
            expr=self.plate_pact_length / total_plates - self.plate_thickness)

        self.plate_area = Expression(expr=self.area / total_active_plates,
                                     doc='Heat transfer area of single plate')

        self.surface_enlargement_factor = Expression(
            expr=self.plate_area / (self.plate_length * self.plate_width))

        # Channel equivalent diameter
        self.channel_diameter = Expression(expr=2 * self.plate_gap /
                                           self.surface_enlargement_factor,
                                           doc="Channel equivalent diameter")

        # ---------------------------------------------------------------------
        # Fluid velocities
        def rule_port_vel_hot(blk, t):
            return (4 * blk.hot_side.properties_in[t].flow_vol /
                    (Constants.pi * blk.port_diameter**2))

        self.hot_port_velocity = Expression(self.flowsheet().time,
                                            rule=rule_port_vel_hot,
                                            doc='Hot side port velocity')

        def rule_port_vel_cold(blk, t):
            return (4 *
                    pyunits.convert(blk.cold_side.properties_in[t].flow_vol,
                                    to_units=units_meta("flow_vol")) /
                    (Constants.pi * blk.port_diameter**2))

        self.cold_port_velocity = Expression(self.flowsheet().time,
                                             rule=rule_port_vel_cold,
                                             doc='Cold side port velocity')

        def rule_channel_vel_hot(blk, t):
            return (blk.hot_side.properties_in[t].flow_vol /
                    (blk.channels_per_pass * blk.plate_width * blk.plate_gap))

        self.hot_channel_velocity = Expression(self.flowsheet().time,
                                               rule=rule_channel_vel_hot,
                                               doc='Hot side channel velocity')

        def rule_channel_vel_cold(blk, t):
            return (pyunits.convert(blk.cold_side.properties_in[t].flow_vol,
                                    to_units=units_meta("flow_vol")) /
                    (blk.channels_per_pass * blk.plate_width * blk.plate_gap))

        self.cold_channel_velocity = Expression(
            self.flowsheet().time,
            rule=rule_channel_vel_cold,
            doc='Cold side channel velocity')

        # ---------------------------------------------------------------------
        # Reynolds & Prandtl numbers
        # Density cancels out of Reynolds number if mass flow rate is used
        def rule_Re_h(blk, t):
            return (blk.hot_side.properties_in[t].flow_mass *
                    blk.channel_diameter /
                    (blk.channels_per_pass * blk.plate_width * blk.plate_gap *
                     blk.hot_side.properties_in[t].visc_d_phase["Liq"]))

        self.Re_hot = Expression(self.flowsheet().time,
                                 rule=rule_Re_h,
                                 doc='Hot side Reynolds number')

        def rule_Re_c(blk, t):
            return (pyunits.convert(
                blk.cold_side.properties_in[t].flow_mass /
                blk.cold_side.properties_in[t].visc_d_phase["Liq"],
                to_units=units_meta("length")) * blk.channel_diameter /
                    (blk.channels_per_pass * blk.plate_width * blk.plate_gap))

        self.Re_cold = Expression(self.flowsheet().time,
                                  rule=rule_Re_c,
                                  doc='Cold side Reynolds number')

        def rule_Pr_h(blk, t):
            return (blk.hot_side.properties_in[t].cp_mol /
                    blk.hot_side.properties_in[t].mw *
                    blk.hot_side.properties_in[t].visc_d_phase["Liq"] /
                    blk.hot_side.properties_in[t].therm_cond_phase["Liq"])

        self.Pr_hot = Expression(self.flowsheet().time,
                                 rule=rule_Pr_h,
                                 doc='Hot side Prandtl number')

        def rule_Pr_c(blk, t):
            return (blk.cold_side.properties_in[t].cp_mol /
                    blk.cold_side.properties_in[t].mw *
                    blk.cold_side.properties_in[t].visc_d_phase["Liq"] /
                    blk.cold_side.properties_in[t].therm_cond_phase["Liq"])

        self.Pr_cold = Expression(self.flowsheet().time,
                                  rule=rule_Pr_c,
                                  doc='Cold side Prandtl number')

        # ---------------------------------------------------------------------
        # Heat transfer coefficients
        # Parameters for Nusselt number correlation
        self.Nusselt_param_a = Param(initialize=0.4,
                                     domain=PositiveReals,
                                     units=pyunits.dimensionless,
                                     mutable=True,
                                     doc='Nusselt parameter A')
        self.Nusselt_param_b = Param(initialize=0.663,
                                     domain=PositiveReals,
                                     units=pyunits.dimensionless,
                                     mutable=True,
                                     doc='Nusselt parameter B')
        self.Nusselt_param_c = Param(initialize=0.333,
                                     domain=PositiveReals,
                                     units=pyunits.dimensionless,
                                     mutable=True,
                                     doc='Nusselt parameter C')

        # Film heat transfer coefficients
        def rule_hotside_transfer_coeff(blk, t):
            return (blk.hot_side.properties_in[t].therm_cond_phase["Liq"] /
                    blk.channel_diameter * blk.Nusselt_param_a *
                    blk.Re_hot[t]**blk.Nusselt_param_b *
                    blk.Pr_hot[t]**blk.Nusselt_param_c)

        self.heat_transfer_coefficient_hot_side = Expression(
            self.flowsheet().time,
            rule=rule_hotside_transfer_coeff,
            doc='Hot side heat transfer coefficient')

        def rule_coldside_transfer_coeff(blk, t):
            return (pyunits.convert(
                blk.cold_side.properties_in[t].therm_cond_phase["Liq"],
                to_units=units_meta("thermal_conductivity")) /
                    blk.channel_diameter * blk.Nusselt_param_a *
                    blk.Re_cold[t]**blk.Nusselt_param_b *
                    blk.Pr_cold[t]**blk.Nusselt_param_c)

        self.heat_transfer_coefficient_cold_side = Expression(
            self.flowsheet().time,
            rule=rule_coldside_transfer_coeff,
            doc='Cold side heat transfer coefficient')

        # Overall heat transfer coefficient
        def rule_U(blk, t):
            return blk.heat_transfer_coefficient[t] == (
                1.0 / (1.0 / blk.heat_transfer_coefficient_hot_side[t] +
                       blk.plate_gap / blk.plate_therm_cond +
                       1.0 / blk.heat_transfer_coefficient_cold_side[t]))

        self.overall_heat_transfer_eq = Constraint(
            self.flowsheet().time,
            rule=rule_U,
            doc='Calculations of overall heat transfer coefficient')

        # Effectiveness based on sub-heat exchangers
        # Divide NTU by number of channels per pass
        def rule_Ecf(blk, t):
            if blk.number_of_passes.value % 2 == 0:
                return (
                    blk.effectiveness[t] ==
                    (1 - exp(-blk.NTU[t] / blk.channels_per_pass *
                             (1 - blk.Cratio[t]))) /
                    (1 -
                     blk.Cratio[t] * exp(-blk.NTU[t] / blk.channels_per_pass *
                                         (1 - blk.Cratio[t]))))
            elif blk.pass_num.value % 2 == 1:
                return (blk.effectiveness[t] ==
                        (1 - exp(-blk.NTU[t] / blk.channels_per_pass *
                                 (1 + blk.Cratio[t]))) / (1 + blk.Cratio[t]))

        self.effectiveness_correlation = Constraint(
            self.flowsheet().time,
            rule=rule_Ecf,
            doc='Correlation for effectiveness factor')

        # ---------------------------------------------------------------------
        # Pressure drop correlations
        # Friction factor calculation
        self.friction_factor_param_a = Param(initialize=0.0,
                                             units=pyunits.dimensionless,
                                             doc='Friction factor parameter A',
                                             mutable=True)
        self.friction_factor_param_b = Param(initialize=18.29,
                                             units=pyunits.dimensionless,
                                             doc='Friction factor parameter B',
                                             mutable=True)
        self.friction_factor_param_c = Param(initialize=-0.652,
                                             units=pyunits.dimensionless,
                                             doc='Friction factor parameter C',
                                             mutable=True)

        def rule_fric_h(blk, t):
            return (blk.friction_factor_param_a + blk.friction_factor_param_b *
                    blk.Re_hot[t]**(blk.friction_factor_param_c))

        self.friction_factor_hot = Expression(self.flowsheet().time,
                                              rule=rule_fric_h,
                                              doc='Hot side friction factor')

        def rule_fric_c(blk, t):
            return (blk.friction_factor_param_a + blk.friction_factor_param_b *
                    blk.Re_cold[t]**(blk.friction_factor_param_c))

        self.friction_factor_cold = Expression(self.flowsheet().time,
                                               rule=rule_fric_c,
                                               doc='Cold side friction factor')

        def rule_hotside_dP(blk, t):
            return blk.hot_side.deltaP[t] == -(
                (2 * blk.friction_factor_hot[t] *
                 (blk.plate_length + blk.port_diameter) *
                 blk.number_of_passes * blk.hot_channel_velocity[t]**2 *
                 blk.hot_side.properties_in[t].dens_mass /
                 blk.channel_diameter) +
                (0.7 * blk.number_of_passes * blk.hot_port_velocity[t]**2 *
                 blk.hot_side.properties_in[t].dens_mass) +
                (blk.hot_side.properties_in[t].dens_mass *
                 pyunits.convert(Constants.acceleration_gravity,
                                 to_units=units_meta("acceleration")) *
                 (blk.plate_length + blk.port_diameter)))

        self.hot_side_deltaP_eq = Constraint(self.flowsheet().time,
                                             rule=rule_hotside_dP)

        def rule_coldside_dP(blk, t):
            return blk.cold_side.deltaP[t] == -(
                (2 * blk.friction_factor_cold[t] *
                 (blk.plate_length + blk.port_diameter) *
                 blk.number_of_passes * blk.cold_channel_velocity[t]**2 *
                 pyunits.convert(blk.cold_side.properties_in[t].dens_mass,
                                 to_units=units_meta("density_mass")) /
                 blk.channel_diameter) +
                (0.7 * blk.number_of_passes * blk.cold_port_velocity[t]**2 *
                 pyunits.convert(blk.cold_side.properties_in[t].dens_mass,
                                 to_units=units_meta("density_mass"))) +
                (pyunits.convert(blk.cold_side.properties_in[t].dens_mass,
                                 to_units=units_meta("density_mass")) *
                 pyunits.convert(Constants.acceleration_gravity,
                                 to_units=units_meta("acceleration")) *
                 (blk.plate_length + blk.port_diameter)))

        self.cold_side_deltaP_eq = Constraint(self.flowsheet().time,
                                              rule=rule_coldside_dP)
Example #14
0
    def _flow_vol(self):
        def rule_flow_vol(b):
            return sum(b.flow_vol_phase[p] for p in self.params.phase_list)

        self.flow_vol = Expression(rule=rule_flow_vol)
Example #15
0
    def _make_phase_split(self,
                          port=None,
                          phase=None,
                          has_liquid_side_draw=False,
                          has_vapor_side_draw=False,
                          side_sf=None):
        """Method to split and populate the outlet ports with corresponding
           phase values from the mixed stream outlet block."""

        member_list = self.properties_out[0].define_port_members()

        for k in member_list:

            local_name = member_list[k].local_name

            # Create references and populate the intensive variables
            if "flow" not in local_name and "frac" not in local_name \
                    and "enth" not in local_name:
                if not member_list[k].is_indexed():
                    var = self.properties_out[:].\
                        component(local_name)
                else:
                    var = self.properties_out[:].\
                        component(local_name)[...]

                # add the reference and variable name to the port
                ref = Reference(var)
                setattr(self, "_" + k + "_ref", ref)
                port.add(ref, k)

            elif "frac" in local_name:

                # Mole/mass frac is typically indexed
                index_set = member_list[k].index_set()

                # if state var is not mole/mass frac by phase
                if "phase" not in local_name:
                    if "mole" in local_name:  # check mole basis/mass basis

                        # The following conditionals are required when a
                        # mole frac or mass frac is a state var i.e. will be
                        # a port member. This gets a bit tricky when handling
                        # non-conventional systems when you have more than one
                        # liquid or vapor phase. Hence, the logic here is that
                        # the mole frac that should be present in the liquid or
                        # vapor port should be computed by accounting for
                        # multiple liquid or vapor phases if present. For the
                        # classical VLE system, this holds too.
                        if hasattr(self.properties_out[0],
                                   "mole_frac_phase_comp") and \
                            hasattr(self.properties_out[0],
                                    "flow_mol_phase"):
                            flow_phase_comp = False
                            local_name_frac = "mole_frac_phase_comp"
                            local_name_flow = "flow_mol_phase"
                        elif hasattr(self.properties_out[0],
                                     "flow_mol_phase_comp"):
                            flow_phase_comp = True
                            local_name_flow = "flow_mol_phase_comp"
                        else:
                            raise PropertyNotSupportedError(
                                "No mole_frac_phase_comp or flow_mol_phase or"
                                " flow_mol_phase_comp variables encountered "
                                "while building ports for the condenser. ")
                    elif "mass" in local_name:
                        if hasattr(self.properties_out[0],
                                   "mass_frac_phase_comp") and \
                            hasattr(self.properties_out[0],
                                    "flow_mass_phase"):
                            flow_phase_comp = False
                            local_name_frac = "mass_frac_phase_comp"
                            local_name_flow = "flow_mass_phase"
                        elif hasattr(self.properties_out[0],
                                     "flow_mass_phase_comp"):
                            flow_phase_comp = True
                            local_name_flow = "flow_mass_phase_comp"
                        else:
                            raise PropertyNotSupportedError(
                                "No mass_frac_phase_comp or flow_mass_phase or"
                                " flow_mass_phase_comp variables encountered "
                                "while building ports for the condenser.")
                    else:
                        raise PropertyNotSupportedError(
                            "No mass frac or mole frac variables encountered "
                            " while building ports for the condenser. "
                            "phase_frac as a state variable is not "
                            "supported with distillation unit models.")

                    # Rule for mole fraction
                    def rule_mole_frac(self, t, i):
                        if not flow_phase_comp:
                            sum_flow_comp = sum(
                                self.properties_out[t].component(
                                    local_name_frac)[p, i] *
                                self.properties_out[t].component(
                                    local_name_flow)[p] for p in phase)

                            return sum_flow_comp / sum(
                                self.properties_out[t].component(
                                    local_name_flow)[p] for p in phase)
                        else:
                            sum_flow_comp = sum(
                                self.properties_out[t].component(
                                    local_name_flow)[p, i] for p in phase)

                            return sum_flow_comp / sum(
                                self.properties_out[t].component(
                                    local_name_flow)[p, i] for p in phase for i
                                in self.config.property_package.component_list)

                    # add the reference and variable name to the port
                    expr = Expression(self.flowsheet().time,
                                      index_set,
                                      rule=rule_mole_frac)
                    self.add_component("e_mole_frac_" + port.local_name, expr)
                    port.add(expr, k)
                else:

                    # Assumes mole_frac_phase or mass_frac_phase exist as
                    # state vars in the port and therefore access directly
                    # from the state block.
                    var = self.properties_out[:].\
                        component(local_name)[...]

                    # add the reference and variable name to the port
                    ref = Reference(var)
                    setattr(self, "_" + k + "_" + port.local_name + "_ref",
                            ref)
                    port.add(ref, k)
            elif "flow" in local_name:
                if "phase" not in local_name:

                    # Assumes that here the var is total flow or component
                    # flow. However, need to extract the flow by phase from
                    # the state block. Expects to find the var
                    # flow_mol_phase or flow_mass_phase in the state block.

                    # Check if it is not indexed by component list and this
                    # is total flow
                    if not member_list[k].is_indexed():
                        # if state var is not flow_mol/flow_mass
                        # by phase
                        local_name_flow = local_name + "_phase"

                        # Rule to link the flow to the port
                        def rule_flow(self, t):
                            return sum(self.properties_out[t].component(
                                local_name_flow)[p] for p in phase) * (side_sf)

                        # add the reference and variable name to the port
                        expr = Expression(self.flowsheet().time,
                                          rule=rule_flow)
                        self.add_component("e_flow_" + port.local_name, expr)
                        port.add(expr, k)
                    else:
                        # when it is flow comp indexed by component list
                        str_split = local_name.split("_")
                        if len(str_split) == 3 and str_split[-1] == "comp":
                            local_name_flow = str_split[0] + "_" + \
                                str_split[1] + "_phase_" + "comp"

                        # Get the indexing set i.e. component list
                        index_set = member_list[k].index_set()

                        # Rule to link the flow to the port
                        def rule_flow(self, t, i):
                            return sum(self.properties_out[t].component(
                                local_name_flow)[p, i]
                                       for p in phase) * (side_sf)

                        expr = Expression(self.flowsheet().time,
                                          index_set,
                                          rule=rule_flow)
                        self.add_component("e_flow_" + port.local_name, expr)
                        port.add(expr, local_name)
                elif "phase" in local_name:
                    # flow is indexed by phase and comp
                    # Get the indexing sets i.e. component list and phase list
                    component_set = self.config.\
                        property_package.component_list

                    phase_set = self.config.\
                        property_package.phase_list

                    def rule_flow(self, t, p, i):
                        if (phase is self._liquid_set and
                                p in self._liquid_set) or \
                                (phase is self._vapor_set and
                                 p in self._vapor_set) :
                            # pass appropriate phase flow values to port
                            return (self.properties_out[t].component(
                                local_name)[p, i]) * (side_sf)
                        else:
                            # return small number for phase that should not
                            # be in the appropriate port. For example,
                            # the state vars will be flow_mol_phase_comp
                            # which will include all phases. The liq port
                            # should have the correct references to the liq
                            # phase flow but the vapor phase flow should be 0.
                            return 1e-8

                    expr = Expression(self.flowsheet().time,
                                      phase_set,
                                      component_set,
                                      rule=rule_flow)
                    self.add_component("e_" + local_name + port.local_name,
                                       expr)
                    port.add(expr, k)
                else:
                    raise PropertyPackageError(
                        "Unrecognized flow state variable encountered "
                        "while building ports for the tray. Please follow "
                        "the naming convention outlined in the documentation "
                        "for state variables.")
            elif "enth" in local_name:
                if "phase" not in local_name:
                    # assumes total mixture enthalpy (enth_mol or enth_mass)
                    if not member_list[k].is_indexed():
                        # if state var is not enth_mol/enth_mass
                        # by phase, add _phase string to extract the right
                        # value from the state block
                        local_name_phase = local_name + "_phase"
                    else:
                        raise PropertyPackageError(
                            "Enthalpy is indexed but the variable "
                            "name does not reflect the presence of an index. "
                            "Please follow the naming convention outlined "
                            "in the documentation for state variables.")

                    # Rule to link the phase enthalpy to the port.
                    def rule_enth(self, t):
                        return sum(self.properties_out[t].component(
                            local_name_phase)[p] for p in phase)

                    expr = Expression(self.flowsheet().time, rule=rule_enth)
                    self.add_component("e_enth_" + port.local_name, expr)
                    # add the reference and variable name to the port
                    port.add(expr, k)

                elif "phase" in local_name:
                    # assumes enth_mol_phase or enth_mass_phase.
                    # This is an intensive property, you create a direct
                    # reference irrespective of the reflux, distillate and
                    # vap_outlet

                    if not member_list[k].is_indexed():
                        var = self.properties_out[:].\
                            component(local_name)
                    else:
                        var = self.properties_out[:].\
                            component(local_name)[...]

                    # add the reference and variable name to the port
                    ref = Reference(var)
                    setattr(self, "_" + k + "_" + port.local_name + "_ref",
                            ref)
                    port.add(ref, k)
                else:
                    raise PropertyNotSupportedError(
                        "Unrecognized enthalpy state variable encountered "
                        "while building ports for the tray. Only total "
                        "mixture enthalpy or enthalpy by phase are supported.")
Example #16
0
def build():
    # flowsheet set up
    m = ConcreteModel()
    m.fs = FlowsheetBlock(default={'dynamic': False})
    m.fs.properties = props.NaClParameterBlock()
    financials.add_costing_param_block(m.fs)

    # unit models
    m.fs.feed = Feed(default={'property_package': m.fs.properties})
    m.fs.S1 = Separator(default={
        "property_package": m.fs.properties,
        "outlet_list": ['P1', 'PXR']
    })
    m.fs.P1 = Pump(default={'property_package': m.fs.properties})
    m.fs.PXR = PressureExchanger(default={'property_package': m.fs.properties})
    m.fs.P2 = Pump(default={'property_package': m.fs.properties})
    m.fs.M1 = Mixer(
        default={
            "property_package": m.fs.properties,
            "momentum_mixing_type":
            MomentumMixingType.equality,  # booster pump will match pressure
            "inlet_list": ['P1', 'P2']
        })
    m.fs.RO = ReverseOsmosis0D(default={
        "property_package": m.fs.properties,
        "has_pressure_change": True
    })
    m.fs.product = Product(default={'property_package': m.fs.properties})
    m.fs.disposal = Product(default={'property_package': m.fs.properties})

    # additional variables or expressions
    feed_flow_vol_total = m.fs.feed.properties[0].flow_vol
    product_flow_vol_total = m.fs.product.properties[0].flow_vol
    m.fs.recovery = Expression(expr=product_flow_vol_total /
                               feed_flow_vol_total)
    m.fs.annual_water_production = Expression(expr=pyunits.convert(
        product_flow_vol_total, to_units=pyunits.m**3 / pyunits.year) *
                                              m.fs.costing_param.load_factor)
    pump_power_total = m.fs.P1.work_mechanical[0] + m.fs.P2.work_mechanical[0]
    m.fs.specific_energy_consumption = Expression(
        expr=pyunits.convert(pump_power_total, to_units=pyunits.kW) /
        pyunits.convert(product_flow_vol_total,
                        to_units=pyunits.m**3 / pyunits.hr))

    # costing
    m.fs.P1.get_costing(module=financials, pump_type="High pressure")
    m.fs.P2.get_costing(module=financials, pump_type="High pressure")
    m.fs.RO.get_costing(module=financials)
    m.fs.PXR.get_costing(module=financials)
    financials.get_system_costing(m.fs)

    # connections
    m.fs.s01 = Arc(source=m.fs.feed.outlet, destination=m.fs.S1.inlet)
    m.fs.s02 = Arc(source=m.fs.S1.P1, destination=m.fs.P1.inlet)
    m.fs.s03 = Arc(source=m.fs.P1.outlet, destination=m.fs.M1.P1)
    m.fs.s04 = Arc(source=m.fs.M1.outlet, destination=m.fs.RO.inlet)
    m.fs.s05 = Arc(source=m.fs.RO.permeate, destination=m.fs.product.inlet)
    m.fs.s06 = Arc(source=m.fs.RO.retentate,
                   destination=m.fs.PXR.high_pressure_inlet)
    m.fs.s07 = Arc(source=m.fs.PXR.high_pressure_outlet,
                   destination=m.fs.disposal.inlet)
    m.fs.s08 = Arc(source=m.fs.S1.PXR, destination=m.fs.PXR.low_pressure_inlet)
    m.fs.s09 = Arc(source=m.fs.PXR.low_pressure_outlet,
                   destination=m.fs.P2.inlet)
    m.fs.s10 = Arc(source=m.fs.P2.outlet, destination=m.fs.M1.P2)
    TransformationFactory("network.expand_arcs").apply_to(m)

    # scaling
    m.fs.properties.set_default_scaling('flow_mass_phase_comp',
                                        1,
                                        index=('Liq', 'H2O'))
    m.fs.properties.set_default_scaling('flow_mass_phase_comp',
                                        1e2,
                                        index=('Liq', 'NaCl'))
    iscale.calculate_scaling_factors(m)

    return m
Example #17
0
def build(m, section='desalination', pretrt_type='NF', **kwargs):
    if section == 'desalination':
        m.fs.desal_saturation = Block()
        m.fs.desal_saturation.properties = m.fs.prop_eNRTL.build_state_block([0], default={})
        sb_eNRTL = m.fs.desal_saturation.properties[0]
    elif section == 'pretreatment':
        m.fs.pretrt_saturation = Block()
        m.fs.pretrt_saturation.properties = m.fs.prop_eNRTL.build_state_block([0], default={})
        sb_eNRTL = m.fs.pretrt_saturation.properties[0]
    else:
        raise ValueError('{section} is not an expected section for building the saturation index'
                         ''.format(section=section))

    # populate initial values
    populate_eNRTL_state_vars(sb_eNRTL, base='FpcTP')

    # ksp = 3.9e-9  # Gibbs energy gives 3.9e-8, but this fits expectations better
    ksp = 3.2e-9  # This fits expectations even better

    # constraints
    if section == 'desalination':
        sb_dilute = m.fs.tb_pretrt_to_desal.properties_in[0]
        if kwargs['is_twostage']:
            sb_perm = m.fs.mixer_permeate.mixed_state[0]
            sb_conc = m.fs.RO2.feed_side.properties[0, 1]
            sb_conc_inter = m.fs.RO2.feed_side.properties_interface[0, 1]
        else:
            sb_perm = m.fs.RO.mixed_permeate[0]
            sb_conc = m.fs.RO.feed_side.properties[0, 1]
            sb_conc_inter = m.fs.RO.feed_side.properties_interface[0, 1]

        m.fs.desal_saturation.cp_modulus = Expression(
            expr=sb_conc_inter.conc_mass_phase_comp['Liq', 'TDS'] / sb_conc.conc_mass_phase_comp['Liq', 'TDS'])

        # constraints
        m.fs.desal_saturation.eq_temperature = Constraint(
            expr=sb_eNRTL.temperature == sb_conc.temperature)
        m.fs.desal_saturation.eq_pressure = Constraint(
            expr=sb_eNRTL.pressure == sb_conc.pressure)

        if pretrt_type == 'NF':
            # assumes pretreatment uses the ion property basis
            comp_match_dict = {'Na_+': 'Na',
                               'Ca_2+': 'Ca',
                               'Mg_2+': 'Mg',
                               'SO4_2-': 'SO4',
                               'Cl_-': 'Cl',
                               'H2O': 'H2O'}

            @m.fs.desal_saturation.Constraint(comp_match_dict.keys())
            def eq_flow_mol_balance(b, j):
                if j in ['Cl_-', 'Na_+']:
                    bulk_flow = (sb_dilute.flow_mol_phase_comp['Liq', comp_match_dict[j]]
                                 - sb_perm.flow_mass_phase_comp['Liq', 'TDS'] / (58.44e-3 * pyunits.kg / pyunits.mol))
                    return (sb_eNRTL.flow_mol_phase_comp['Liq', j]
                            == bulk_flow * m.fs.desal_saturation.cp_modulus)
                elif j in ['Ca_2+', 'Mg_2+', 'SO4_2-']:
                    return (sb_eNRTL.flow_mol_phase_comp['Liq', j]
                            == sb_dilute.flow_mol_phase_comp['Liq', comp_match_dict[j]]
                            * m.fs.desal_saturation.cp_modulus)
                elif j == 'H2O':
                    return (sb_eNRTL.flow_mol_phase_comp['Liq', j] ==
                            sb_conc.flow_mol_phase_comp['Liq', 'H2O'])
        elif pretrt_type == 'softening':
            # assumes pretreatment uses the softening property basis
            comp_match_dict = {'Na_+': 'NaCl',
                               'Ca_2+': 'Ca(HCO3)2',
                               'Mg_2+': 'Mg(HCO3)2',
                               'SO4_2-': 'SO4_2-',
                               'Cl_-': 'Cl_-',
                               'H2O': 'H2O'}

            @m.fs.desal_saturation.Constraint(comp_match_dict.keys())
            def eq_flow_mol_balance(b, j):
                if j == 'Na_+':
                    bulk_flow = (sb_dilute.flow_mol_phase_comp['Liq', 'NaCl']
                                 - sb_perm.flow_mass_phase_comp['Liq', 'TDS'] / (58.44e-3 * pyunits.kg / pyunits.mol))
                    return (sb_eNRTL.flow_mol_phase_comp['Liq', j]
                            == bulk_flow * m.fs.desal_saturation.cp_modulus)
                if j in 'Cl_-':
                    bulk_flow = (sb_dilute.flow_mol_phase_comp['Liq', 'Cl_-']
                            + sb_dilute.flow_mol_phase_comp['Liq', 'NaCl']
                            - sb_perm.flow_mass_phase_comp['Liq', 'TDS'] / (58.44e-3 * pyunits.kg / pyunits.mol))
                    return (sb_eNRTL.flow_mol_phase_comp['Liq', j]
                            == bulk_flow * m.fs.desal_saturation.cp_modulus)
                elif j in ['Ca_2+', 'Mg_2+', 'SO4_2-']:
                    return (sb_eNRTL.flow_mol_phase_comp['Liq', j]
                            == sb_dilute.flow_mol_phase_comp['Liq', comp_match_dict[j]]
                            * m.fs.desal_saturation.cp_modulus)
                elif j == 'H2O':
                    return (sb_eNRTL.flow_mol_phase_comp['Liq', j] ==
                            sb_conc.flow_mol_phase_comp['Liq', 'H2O'])

        m.fs.desal_saturation.saturation_index = Var(
            initialize=0.5,
            bounds=(1e-8, 10),
            units=pyunits.dimensionless,
            doc="Gypsum saturation index")

        m.fs.desal_saturation.eq_saturation_index = Constraint(
            expr=m.fs.desal_saturation.saturation_index
                 == sb_eNRTL.act_phase_comp["Liq", "Ca_2+"]
                 * sb_eNRTL.act_phase_comp["Liq", "SO4_2-"]
                 * sb_eNRTL.act_phase_comp["Liq", "H2O"] ** 2
                 / ksp)

    elif section == 'pretreatment':
        comp_match_dict = {'Na_+': 'Na',
                           'Ca_2+': 'Ca',
                           'Mg_2+': 'Mg',
                           'SO4_2-': 'SO4',
                           'Cl_-': 'Cl',
                           'H2O': 'H2O'}

        sb_conc = m.fs.NF.feed_side.properties_out[0]

        m.fs.pretrt_saturation.eq_temperature = Constraint(
            expr=sb_eNRTL.temperature == sb_conc.temperature)
        m.fs.pretrt_saturation.eq_pressure = Constraint(
            expr=sb_eNRTL.pressure == sb_conc.pressure)

        @m.fs.pretrt_saturation.Constraint(comp_match_dict.keys())
        def eq_flow_mol_balance(b, j):
                return (sb_eNRTL.flow_mol_phase_comp['Liq', j] ==
                        sb_conc.flow_mol_phase_comp['Liq', comp_match_dict[j]])

        m.fs.pretrt_saturation.saturation_index = Var(
            initialize=0.5,
            bounds=(1e-8, 1e6),
            units=pyunits.dimensionless,
            doc="Gypsum saturation index")

        m.fs.pretrt_saturation.eq_saturation_index = Constraint(
            expr=m.fs.pretrt_saturation.saturation_index
                 == sb_eNRTL.act_phase_comp["Liq", "Ca_2+"]
                 * sb_eNRTL.act_phase_comp["Liq", "SO4_2-"]
                 * sb_eNRTL.act_phase_comp["Liq", "H2O"] ** 2
                 / ksp)
Example #18
0
model.y_is_leq_sum_x = Constraint(rule=y_is_leq_sum_x_rule)


def slacker_rule(model):
    return model.a * model.y + model.slackbool >= model.b


model.slacker = Constraint(rule=slacker_rule)


def FirstStageCost_rule(model):
    return 0


model.FirstStageCost = Expression(rule=FirstStageCost_rule)


def SecondStageCost_rule(model):
    return model.c * model.y + model.M * model.slackbool


model.SecondStageCost = Expression(rule=SecondStageCost_rule)


def Obj_rule(model):
    return model.FirstStageCost + model.SecondStageCost


model.Obj = Objective(rule=Obj_rule)
Example #19
0
def add_model_components(m, d, scenario_directory, subproblem, stage):
    """
    The following Pyomo model components are defined in this module:

    +-------------------------------------------------------------------------+
    | Sets                                                                    |
    +=========================================================================+
    | | :code:`GEN_NEW_LIN_VNTS`                                              |
    |                                                                         |
    | A two-dimensional set of project-vintage combinations to describe the   |
    | periods in time when project capacity can be built in the optimization. |
    +-------------------------------------------------------------------------+
    | | :code:`GEN_NEW_LIN_VNTS_W_MIN_CONSTRAINT`                             |
    |                                                                         |
    | Two-dimensional set of project-vintage combinations to describe all     |
    | possible project-vintage combinations for projects with a cumulative    |
    | minimum build capacity specified.                                       |
    +-------------------------------------------------------------------------+
    | | :code:`GEN_NEW_LIN_VNTS_W_MAX_CONSTRAINT`                             |
    |                                                                         |
    | Two-dimensional set of project-vintage combinations to describe all     |
    | possible project-vintage combinations for projects with a cumulative    |
    | maximum build capacity specified.                                       |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Required Input Params                                                   |
    +=========================================================================+
    | | :code:`gen_new_lin_lifetime_yrs_by_vintage`                           |
    | | *Defined over*: :code:`GEN_NEW_LIN_VNTS`                              |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The project's lifetime, i.e. how long project capacity of a particular  |
    | vintage remains operational.                                            |
    +-------------------------------------------------------------------------+
    | | :code:`gen_new_lin_annualized_real_cost_per_mw_yr`                    |
    | | *Defined over*: :code:`GEN_NEW_LIN_VNTS`                              |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The project's cost to build new capacity in annualized real dollars in  |
    | per MW.                                                                 |
    +-------------------------------------------------------------------------+

    .. note:: The cost input to the model is a levelized cost per unit
        capacity. This annualized cost is incurred in each period of the study
        (and multiplied by the number of years the period represents) for
        the duration of the project's lifetime. It is up to the user to
        ensure that the :code:`gen_new_lin_lifetime_yrs_by_vintage` and
        :code:`gen_new_lin_annualized_real_cost_per_mw_yr` parameters are
        consistent.

    +-------------------------------------------------------------------------+
    | Optional Input Params                                                   |
    +=========================================================================+
    | | :code:`gen_new_lin_min_cumulative_new_build_mw`                       |
    | | *Defined over*: :code:`GEN_NEW_LIN_VNTS`                              |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The minimum cumulative amount of capacity (in MW) that must be built    |
    | for a project by a certain period.                                      |
    +-------------------------------------------------------------------------+
    | | :code:`gen_new_lin_max_cumulative_new_build_mw`                       |
    | | *Defined over*: :code:`GEN_NEW_LIN_VNTS`                              |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The maximum cumulative amount of capacity (in MW) that must be built    |
    | for a project by a certain period.                                      |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Derived Sets                                                            |
    +=========================================================================+
    | | :code:`OPR_PRDS_BY_GEN_NEW_LIN_VINTAGE`                               |
    | | *Defined over*: :code:`GEN_NEW_LIN_VNTS`                              |
    |                                                                         |
    | Indexed set that describes the operational periods for each possible    |
    | project-vintage combination, based on the                               |
    | :code:`gen_new_lin_lifetime_yrs_by_vintage`. For instance, capacity of  |
    | the 2020 vintage with lifetime of 30 years will be assumed operational  |
    | starting Jan 1, 2020 and through Dec 31, 2049, but will *not* be        |
    | operational in 2050.                                                    |
    +-------------------------------------------------------------------------+
    | | :code:`GEN_NEW_LIN_OPR_PRDS`                                          |
    |                                                                         |
    | Two-dimensional set that includes the periods when project capacity of  |
    | any vintage *could* be operational if built. This set is added to the   |
    | list of sets to join to get the final :code:`PRJ_OPR_PRDS` set defined  |
    | in **gridpath.project.capacity.capacity**.                              |
    +-------------------------------------------------------------------------+
    | | :code:`GEN_NEW_LIN_VNTS_OPR_IN_PERIOD`                                |
    | | *Defined over*: :code:`PERIODS`                                       |
    |                                                                         |
    | Indexed set that describes the project-vintages that could be           |
    | operational in each period based on the                                 |
    | :code:`gen_new_lin_lifetime_yrs_by_vintage`.                            |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Variables                                                               |
    +=========================================================================+
    | | :code:`GenNewLin_Build_MW`                                            |
    | | *Defined over*: :code:`GEN_NEW_LIN_VNTS`                              |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | Determines how much capacity of each possible vintage is built at each  |
    | gen_new_lin project.                                                    |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Expressions                                                             |
    +=========================================================================+
    | | :code:`GenNewLin_Capacity_MW`                                         |
    | | *Defined over*: :code:`GEN_NEW_LIN_OPR_PRDS`                          |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The capacity of a new-build generator in a given operational period is  |
    | equal to the sum of all capacity-build of vintages operational in that  |
    | period.                                                                 |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Constraints                                                             |
    +=========================================================================+
    | | :code:`GenNewLin_Min_Cum_Build_Constraint`                            |
    | | *Defined over*: :code:`GEN_NEW_LIN_VNTS_W_MIN_CONSTRAINT`             |
    |                                                                         |
    | Ensures that certain amount of capacity is built by a certain period,   |
    | based on :code:`gen_new_lin_min_cumulative_new_build_mw`.               |
    +-------------------------------------------------------------------------+
    | | :code:`GenNewLin_Max_Cum_Build_Constraint`                            |
    | | *Defined over*: :code:`GEN_NEW_LIN_VNTS_W_MAX_CONSTRAINT`             |
    |                                                                         |
    | Limits the amount of capacity built by a certain period, based on       |
    | :code:`gen_new_lin_max_cumulative_new_build_mw`.                        |
    +-------------------------------------------------------------------------+


    """

    # Sets
    ###########################################################################

    m.GEN_NEW_LIN_VNTS = Set(dimen=2, within=m.PROJECTS * m.PERIODS)

    # TODO: rename vintage to period since the constraint is by
    #  project-period, not project-vintage?
    m.GEN_NEW_LIN_VNTS_W_MIN_CONSTRAINT = Set(dimen=2,
                                              within=m.GEN_NEW_LIN_VNTS)

    m.GEN_NEW_LIN_VNTS_W_MAX_CONSTRAINT = Set(dimen=2,
                                              within=m.GEN_NEW_LIN_VNTS)

    # Required Params
    ###########################################################################

    m.gen_new_lin_lifetime_yrs_by_vintage = Param(m.GEN_NEW_LIN_VNTS,
                                                  within=NonNegativeReals)

    m.gen_new_lin_annualized_real_cost_per_mw_yr = Param(
        m.GEN_NEW_LIN_VNTS, within=NonNegativeReals)

    # Optional Params
    ###########################################################################

    m.gen_new_lin_min_cumulative_new_build_mw = Param(
        m.GEN_NEW_LIN_VNTS_W_MIN_CONSTRAINT, within=NonNegativeReals)

    m.gen_new_lin_max_cumulative_new_build_mw = Param(
        m.GEN_NEW_LIN_VNTS_W_MAX_CONSTRAINT, within=NonNegativeReals)

    # Derived Sets
    ###########################################################################

    m.OPR_PRDS_BY_GEN_NEW_LIN_VINTAGE = Set(
        m.GEN_NEW_LIN_VNTS,
        initialize=operational_periods_by_generator_vintage)

    m.GEN_NEW_LIN_OPR_PRDS = Set(dimen=2,
                                 initialize=gen_new_lin_operational_periods)

    m.GEN_NEW_LIN_VNTS_OPR_IN_PERIOD = Set(
        m.PERIODS,
        dimen=2,
        initialize=gen_new_lin_vintages_operational_in_period)

    # Variables
    ###########################################################################

    m.GenNewLin_Build_MW = Var(m.GEN_NEW_LIN_VNTS, within=NonNegativeReals)

    # Expressions
    ###########################################################################

    m.GenNewLin_Capacity_MW = Expression(m.GEN_NEW_LIN_OPR_PRDS,
                                         rule=gen_new_lin_capacity_rule)

    # Constraints
    ###########################################################################

    m.GenNewLin_Min_Cum_Build_Constraint = Constraint(
        m.GEN_NEW_LIN_VNTS_W_MIN_CONSTRAINT, rule=min_cum_build_rule)

    m.GenNewLin_Max_Cum_Build_Constraint = Constraint(
        m.GEN_NEW_LIN_VNTS_W_MAX_CONSTRAINT, rule=max_cum_build_rule)

    # Dynamic Components
    ###########################################################################

    # Add to list of sets we'll join to get the final
    # PRJ_OPR_PRDS set
    getattr(d, capacity_type_operational_period_sets).append(
        "GEN_NEW_LIN_OPR_PRDS", )
Example #20
0
def add_model_components(m, d, scenario_directory, subproblem, stage):
    """
    The following Pyomo model components are defined in this module:

    +-------------------------------------------------------------------------+
    | Input Params                                                            |
    +=========================================================================+
    | | :code:`ramp_tuning_cost_per_mw`                                       |
    | | *Default*: :code:`0`                                                  |
    |                                                                         |
    | The tuning cost for ramping in $ per MW of ramp. The cost is the same   |
    | for upward and downward ramping.                                        |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Variables                                                               |
    +=========================================================================+
    | | :code:`Ramp_Up_Tuning_Cost`                                           |
    | | *Defined over*: :code:`PRJ_OPR_TMPS`                                  |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | This variable represents the total upward ramping tuning cost for each  |
    | project in each operational timepoint.                                  |
    +-------------------------------------------------------------------------+
    | | :code:`Ramp_Up_Tuning_Cost`                                           |
    | | *Defined over*: :code:`PRJ_OPR_TMPS`                                  |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | This variable represents the total downwward ramping tuning cost for    |
    | each project in each operational timepoint.                             |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Expressions                                                             |
    +=========================================================================+
    | | :code:`Ramp_Expression`                                               |
    | | *Defined over*: :code:`PRJ_OPR_TMPS`                                  |
    |                                                                         |
    | This expression pulls the ramping expression from the appropriate       |
    | operational type module. It represents the difference in power output   |
    | (in MW) between 2 timepoints; i.e. a positive number means upward ramp  |
    | and a negative number means downward ramp. For simplicity, we only look |
    | at the difference in power setpoints, i.e. ignore the effect of         |
    | providing any reserves.                                                 |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Constraints                                                             |
    +=========================================================================+
    | | :code:`Ramp_Up_Tuning_Cost_Constraint`                                |
    | | *Defined over*: :code:`PRJ_OPR_TMPS`                                  |
    |                                                                         |
    | Sets the upward ramping tuning cost to be equal to the ramp expression  |
    | times the tuning cost (for the appropriate operational types only).     |
    +-------------------------------------------------------------------------+
    | | :code:`Ramp_Down_Tuning_Cost_Constraint`                              |
    | | *Defined over*: :code:`PRJ_OPR_TMPS`                                  |
    |                                                                         |
    | Sets the downward ramping tuning cost to be equal to the ramp           |
    | expression times the tuning cost (for the appropriate operational types |
    | only).                                                                  |
    +-------------------------------------------------------------------------+

    """

    # Dynamic Inputs
    ###########################################################################

    required_operational_modules = get_required_subtype_modules_from_projects_file(
        scenario_directory=scenario_directory,
        subproblem=subproblem,
        stage=stage,
        which_type="operational_type",
    )

    imported_operational_modules = load_operational_type_modules(
        required_operational_modules)

    # Input Params
    ###########################################################################

    m.ramp_tuning_cost_per_mw = Param(default=0)

    # Expressions
    ###########################################################################

    def ramp_rule(mod, g, tmp):
        gen_op_type = mod.operational_type[g]
        return imported_operational_modules[gen_op_type].power_delta_rule(
            mod, g, tmp)

    m.Ramp_Expression = Expression(m.PRJ_OPR_TMPS, rule=ramp_rule)

    # Variables
    ###########################################################################

    m.Ramp_Up_Tuning_Cost = Var(m.PRJ_OPR_TMPS, within=NonNegativeReals)

    m.Ramp_Down_Tuning_Cost = Var(m.PRJ_OPR_TMPS, within=NonNegativeReals)

    # Constraints
    ###########################################################################

    m.Ramp_Up_Tuning_Cost_Constraint = Constraint(m.PRJ_OPR_TMPS,
                                                  rule=ramp_up_rule)

    m.Ramp_Down_Tuning_Cost_Constraint = Constraint(m.PRJ_OPR_TMPS,
                                                    rule=ramp_down_rule)
Example #21
0
def add_model_components(m, d, scenario_directory, subproblem, stage):
    """
    The following Pyomo model components are defined in this module:

     +-------------------------------------------------------------------------+
    | Sets                                                                    |
    +=========================================================================+
    | | :code:`VAR_OM_COST_SIMPLE_PRJ_OPR_TMPS`                               |
    | | *Within*: :code:`PRJ_OPR_TMPS`                                        |
    |                                                                         |
    | The two-dimensional set of projects for which a simple variable O&M     |
    | cost is specified and their operational timepoints.                     |
    +-------------------------------------------------------------------------+
    | | :code:`VAR_OM_COST_CURVE_PRJS_OPR_TMPS_SGMS`                          |
    |                                                                         |
    | The three-dimensional set of projects for which a VOM cost curve is     |
    | specified along with the VOM curve segments and the project             |
    | operational timepoints.                                                 |
    +-------------------------------------------------------------------------+
    | | :code:`VAR_OM_COST_CURVE_PRJS_OPR_TMPS`                               |
    | | *Within*: :code:`PRJ_OPR_TMPS`                                        |
    |                                                                         |
    | The two-dimensional set of projects for which a VOM cost curve is       |
    | specified along with their operational timepoints.                      |
    +-------------------------------------------------------------------------+
    | | :code:`VAR_OM_COST_ALL_PRJS_OPR_TMPS`                                 |
    | | *Within*: :code:`PRJ_OPR_TMPS`                                        |
    |                                                                         |
    | The two-dimensional set of projects for which either or both a simple   |
    | VOM or a VOM curve is specified along with their operational            |
    | timepoints.                                                             |
    +-------------------------------------------------------------------------+
    | | :code:`STARTUP_COST_PRJ_OPR_TMPS`                                     |
    | | *Within*: :code:`PRJ_OPR_TMPS`                                        |
    |                                                                         |
    | The two-dimensional set of projects for which a startup cost is         |
    | specified along with their operational timepoints.                      |
    +-------------------------------------------------------------------------+
    | | :code:`SHUTDOWN_COST_PRJ_OPR_TMPS`                                    |
    | | *Within*: :code:`PRJ_OPR_TMPS`                                        |
    |                                                                         |
    | The two-dimensional set of projects for which a shutdown cost curve is  |
    | specified along with their operational timepoints.                      |
    +-------------------------------------------------------------------------+
    | | :code:`VIOL_ALL_PRJ_OPR_TMPS`                                         |
    | | *Within*: :code:`PRJ_OPR_TMPS`                                        |
    |                                                                         |
    | The two-dimensional set of projects for which an operational constraint |
    | can be violated along with their operational timepoints.                |
    +-------------------------------------------------------------------------+
    | | :code:`CURTAILMENT_COST_PRJ_OPR_TMPS`                                 |
    | | *Within*: :code:`PRJ_OPR_TMPS`                                        |
    |                                                                         |
    | The two-dimensional set of projects for which an curtailment costs are  |
    | incurred along with their operational timepoints.                       |
    +-------------------------------------------------------------------------+

    |                                                                         |

    +-------------------------------------------------------------------------+
    | Variables                                                               |
    +=========================================================================+
    | | :code:`Variable_OM_Curve_Cost`                                        |
    | | *Defined over*: :code:`VAR_OM_COST_CURVE_PRJS_OPR_TMPS`               |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | Variable cost in each operational timepoint of projects with a VOM cost |
    | curve.                                                                  |
    +-------------------------------------------------------------------------+

    |                                                                         |

    +-------------------------------------------------------------------------+
    | Constraints                                                             |
    +=========================================================================+
    | | :code:`Variable_OM_Curve_Constraint`                                  |
    | | *Defined over*: :code:`VAR_OM_COST_CURVE_PRJS_OPR_TMPS_SGMS`          |
    |                                                                         |
    | Determines variable cost from the project in each timepoint based on    |
    | its VOM curve.                                                          |
    +-------------------------------------------------------------------------+

    |                                                                         |

    +-------------------------------------------------------------------------+
    | Expressions                                                             |
    +=========================================================================+
    | | :code:`Variable_OM_Cost`                                              |
    | | *Defined over*: :code:`VAR_OM_COST_ALL_PRJS_OPR_TMPS`                 |
    |                                                                         |
    | This is the variable cost incurred in each operational timepoints for   |
    | projects for which either a simple VOM or a VOM curve is specified.     |
    | If both are specified, the two are additive. We obtain the simple VOM   |
    | by calling the *variable_om_cost_rule* method of a project's            |
    | *operational_type* module. We obtain the VOM curve cost by calling the  |
    | *variable_om_cost_by_ll_rule* method of a project's operational type,   |
    | using that to create the *Variable_OM_Curve_Constraint* on the          |
    | Variable_OM_Curve_Cost variable, and the using the variable in this     |
    | expression.                                                             |
    +-------------------------------------------------------------------------+
    | | :code:`Fuel_Cost`                                                     |
    | | *Defined over*: :code:`FUEL_PRJ_OPR_TMPS`                             |
    |                                                                         |
    | This expression defines the fuel cost of a project in all of its        |
    | operational timepoints. We obtain the expression by calling the         |
    | *fuel_cost_rule* method of a project's *operational_type* module.       |
    +-------------------------------------------------------------------------+
    | | :code:`Startup_Cost`                                                  |
    | | *Defined over*: :code:`STARTUP_COST_PRJ_OPR_TMPS`                     |
    |                                                                         |
    | This expression defines the startup cost of a project in all of its     |
    | operational timepoints. We obtain the expression by calling the         |
    | *startup_cost_rule* method of a project's *operational_type* module.    |
    +-------------------------------------------------------------------------+
    | | :code:`Shutdown_Cost`                                                 |
    | | *Defined over*: :code:`SHUTDOWN_COST_PRJ_OPR_TMPS`                    |
    |                                                                         |
    | This expression defines the shutdown cost of a project in all of its    |
    | operational timepoints. We obtain the expression by calling the         |
    | *shutdown_cost_rule* method of a project's *operational_type* module.   |
    +-------------------------------------------------------------------------+
    | | :code:`Operational_Violation_Cost`                                    |
    | | *Defined over*: :code:`VIOL_ALL_PRJ_OPR_TMPS`                         |
    |                                                                         |
    | This expression defines the operational constraint violation cost of a  |
    | project in all of its operational timepoints. We obtain the expression  |
    | by calling the *operational_violation_cost_rule* method of a project's  |
    | *operational_type* module.                                              |
    +-------------------------------------------------------------------------+
    | | :code:`Curtailment_Cost`                                              |
    | | *Defined over*: :code:`CURTAILMENT_COST_PRJ_OPR_TMPS`                 |
    |                                                                         |
    | This expression defines the curtailment cost of a project in all of its |
    | operational timepoints. We obtain the expression by calling the         |
    | *curtailment_cost_rule* method of a project's *operational_type* module.|
    +-------------------------------------------------------------------------+

    """

    # Dynamic Inputs
    ###########################################################################

    required_operational_modules = get_required_subtype_modules_from_projects_file(
        scenario_directory=scenario_directory,
        subproblem=subproblem,
        stage=stage,
        which_type="operational_type")

    imported_operational_modules = load_operational_type_modules(
        required_operational_modules)

    # Sets
    ###########################################################################

    m.VAR_OM_COST_SIMPLE_PRJ_OPR_TMPS = Set(
        dimen=2,
        within=m.PRJ_OPR_TMPS,
        initialize=lambda mod: [(p, tmp) for (p, tmp) in mod.PRJ_OPR_TMPS
                                if p in mod.VAR_OM_COST_SIMPLE_PRJS])

    m.VAR_OM_COST_CURVE_PRJS_OPR_TMPS_SGMS = Set(
        dimen=3,
        initialize=lambda mod: list(
            set((g, tmp, s) for (g, tmp) in mod.PRJ_OPR_TMPS
                for _g, p, s in mod.VAR_OM_COST_CURVE_PRJS_PRDS_SGMS
                if g == _g and mod.period[tmp] == p)))

    m.VAR_OM_COST_CURVE_PRJS_OPR_TMPS = Set(
        dimen=2,
        within=m.PRJ_OPR_TMPS,
        initialize=lambda mod: list(
            set((g, tmp)
                for (g, tmp, s) in mod.VAR_OM_COST_CURVE_PRJS_OPR_TMPS_SGMS)))

    # All VOM projects
    m.VAR_OM_COST_ALL_PRJS_OPR_TMPS = Set(
        within=m.PRJ_OPR_TMPS,
        initialize=lambda mod: list(
            set(mod.VAR_OM_COST_SIMPLE_PRJ_OPR_TMPS
                | mod.VAR_OM_COST_CURVE_PRJS_OPR_TMPS)))

    m.STARTUP_COST_PRJ_OPR_TMPS = Set(
        dimen=2,
        within=m.PRJ_OPR_TMPS,
        initialize=lambda mod: [(p, tmp) for (p, tmp) in mod.PRJ_OPR_TMPS
                                if p in mod.STARTUP_COST_PRJS])

    m.SHUTDOWN_COST_PRJ_OPR_TMPS = Set(
        dimen=2,
        within=m.PRJ_OPR_TMPS,
        initialize=lambda mod: [(p, tmp) for (p, tmp) in mod.PRJ_OPR_TMPS
                                if p in mod.SHUTDOWN_COST_PRJS])

    m.VIOL_ALL_PRJ_OPR_TMPS = Set(
        dimen=2,
        within=m.PRJ_OPR_TMPS,
        initialize=lambda mod: [(p, tmp) for (p, tmp) in mod.PRJ_OPR_TMPS
                                if p in mod.VIOL_ALL_PRJS])

    m.CURTAILMENT_COST_PRJ_OPR_TMPS = Set(
        dimen=2,
        within=m.PRJ_OPR_TMPS,
        initialize=lambda mod: [(p, tmp) for (p, tmp) in mod.PRJ_OPR_TMPS
                                if p in mod.CURTAILMENT_COST_PRJS])

    # Variables
    ###########################################################################

    m.Variable_OM_Curve_Cost = Var(m.VAR_OM_COST_CURVE_PRJS_OPR_TMPS,
                                   within=NonNegativeReals)

    # Constraints
    ###########################################################################

    def variable_om_cost_curve_constraint_rule(mod, prj, tmp, s):
        """
        **Constraint Name**: GenCommitBin_Variable_OM_Constraint
        **Enforced Over**: GEN_COMMIT_BIN_VOM_PRJS_OPR_TMPS_SGMS

        Variable O&M cost by loading level is set by piecewise linear
        representation of the input/output curve (variable O&M cost vs.loading
        level).

        Note: we assume that when projects are derated for availability, the
        input/output curve is derated by the same amount. The implicit
        assumption is that when a generator is de-rated, some of its units
        are out rather than it being forced to run below minimum stable level
        at very costly operating points.
        """
        gen_op_type = mod.operational_type[prj]
        if hasattr(imported_operational_modules[gen_op_type],
                   "variable_om_cost_by_ll_rule"):
            var_cost_by_ll = imported_operational_modules[gen_op_type]. \
                variable_om_cost_by_ll_rule(mod, prj, tmp, s)
        else:
            var_cost_by_ll = \
                op_type.variable_om_cost_by_ll_rule(mod, prj, tmp, s)

        return mod.Variable_OM_Curve_Cost[prj, tmp] \
            >= var_cost_by_ll

    m.Variable_OM_Curve_Constraint = Constraint(
        m.VAR_OM_COST_CURVE_PRJS_OPR_TMPS_SGMS,
        rule=variable_om_cost_curve_constraint_rule)

    # Expressions
    ###########################################################################

    def variable_om_cost_rule(mod, prj, tmp):
        """
        **Expression Name**: Variable_OM_Cost
        **Defined Over**: VAR_OM_COST_ALL_PRJS_OPR_TMPS

        This is the variable cost incurred in each operational timepoints for
        projects for which either a simple VOM or a VOM curve is specified.
        If both are specified, the two are additive.
        """

        # Simple VOM cost
        gen_op_type = mod.operational_type[prj]
        if prj in mod.VAR_OM_COST_SIMPLE_PRJS:
            if hasattr(imported_operational_modules[gen_op_type],
                       "variable_om_cost_rule"):
                var_cost_simple = imported_operational_modules[gen_op_type]. \
                    variable_om_cost_rule(mod, prj, tmp)
            else:
                var_cost_simple = op_type.variable_om_cost_rule(mod, prj, tmp)
        else:
            var_cost_simple = 0

        # VOM curve cost
        if prj in mod.VAR_OM_COST_CURVE_PRJS:
            var_cost_curve = mod.Variable_OM_Curve_Cost[prj, tmp]
        else:
            var_cost_curve = 0

        # The two are additive
        return var_cost_simple + var_cost_curve

    m.Variable_OM_Cost = Expression(m.VAR_OM_COST_ALL_PRJS_OPR_TMPS,
                                    rule=variable_om_cost_rule)

    def fuel_cost_rule(mod, prj, tmp):
        """
        **Expression Name**: Fuel_Cost
        **Defined Over**: FUEL_PRJS_OPR_TMPS
        """
        return mod.Total_Fuel_Burn_MMBtu[prj, tmp] * \
            mod.fuel_price_per_mmbtu[
                mod.fuel[prj],
                mod.period[tmp],
                mod.month[tmp]
            ]

    m.Fuel_Cost = Expression(m.FUEL_PRJ_OPR_TMPS, rule=fuel_cost_rule)

    def startup_cost_rule(mod, prj, tmp):
        """
        Startup costs are defined for some operational types while they are
        zero for others. Get the appropriate expression for each generator
        based on its operational type.
        """
        gen_op_type = mod.operational_type[prj]

        if prj in mod.STARTUP_COST_SIMPLE_PRJS:
            if hasattr(imported_operational_modules[gen_op_type],
                       "startup_cost_simple_rule"):
                startup_cost_simple = \
                    imported_operational_modules[gen_op_type]. \
                    startup_cost_simple_rule(mod, prj, tmp)
            else:
                startup_cost_simple = \
                    op_type.startup_cost_simple_rule(mod, prj, tmp)
        else:
            startup_cost_simple = 0

        if prj in mod.STARTUP_BY_ST_PRJS:
            if hasattr(imported_operational_modules[gen_op_type],
                       "startup_cost_by_st_rule"):
                startup_cost_by_st = \
                    imported_operational_modules[gen_op_type]. \
                    startup_cost_by_st_rule(mod, prj, tmp)
            else:
                startup_cost_by_st = \
                    op_type.startup_cost_by_st_rule(mod, prj, tmp)
        else:
            startup_cost_by_st = 0

        return startup_cost_simple + startup_cost_by_st

    m.Startup_Cost = Expression(m.STARTUP_COST_PRJ_OPR_TMPS,
                                rule=startup_cost_rule)

    def shutdown_cost_rule(mod, prj, tmp):
        """
        Shutdown costs are defined for some operational types while they are
        zero for others. Get the appropriate expression for each generator
        based on its operational type.
        """
        gen_op_type = mod.operational_type[prj]
        if hasattr(imported_operational_modules[gen_op_type],
                   "shutdown_cost_rule"):
            return imported_operational_modules[gen_op_type]. \
                shutdown_cost_rule(mod, prj, tmp)
        else:
            return op_type.shutdown_cost_rule(mod, prj, tmp)

    m.Shutdown_Cost = Expression(m.SHUTDOWN_COST_PRJ_OPR_TMPS,
                                 rule=shutdown_cost_rule)

    def operational_violation_cost_rule(mod, prj, tmp):
        """
        Get any operational constraint violation costs.
        """
        gen_op_type = mod.operational_type[prj]
        if hasattr(imported_operational_modules[gen_op_type],
                   "operational_violation_cost_rule"):
            return imported_operational_modules[gen_op_type]. \
                operational_violation_cost_rule(mod, prj, tmp)
        else:
            return op_type.operational_violation_cost_rule(mod, prj, tmp)

    m.Operational_Violation_Cost = Expression(
        m.VIOL_ALL_PRJ_OPR_TMPS, rule=operational_violation_cost_rule)

    def curtailment_cost_rule(mod, prj, tmp):
        """
        Curtailment costs are defined for some operational types while they are
        zero for others. Get the appropriate expression for each generator
        based on its operational type.
        """
        gen_op_type = mod.operational_type[prj]
        if hasattr(imported_operational_modules[gen_op_type],
                   "curtailment_cost_rule"):
            return imported_operational_modules[gen_op_type]. \
                curtailment_cost_rule(mod, prj, tmp)
        else:
            return op_type.curtailment_cost_rule(mod, prj, tmp)

    m.Curtailment_Cost = Expression(m.CURTAILMENT_COST_PRJ_OPR_TMPS,
                                    rule=curtailment_cost_rule)
Example #22
0
    def _fug_liq(self):
        def fug_liq_rule(b, i):
            return b.pressure_sat[i] * b.mole_frac_phase['Liq', i]

        self.fug_liq = Expression(self._params.component_list,
                                  rule=fug_liq_rule)
Example #23
0
    def __init__(self, *args, **kwds):
        """
        =============== ===================================================================
        Variables       Documentation
        =============== ===================================================================
        p               energy derivative with respect to time
        e               energy in battery
        =============== ===================================================================

        =============== ===================================================================
        Derivative Var  Documentation
        =============== ===================================================================
        de              variation of energy  with respect to time
        dp              variation of the battery power with respect to time
        =============== ===================================================================

        =============== ===================================================================
        Parameters      Documentation
        =============== ===================================================================
        emin            minimum energy (kWh)
        emax            maximal energy
        socmin          minimum soc
        socmax          maximal soc
        soc0            initial state
        socf            final state
        dpdmax          maximal discharging power
        dpcmax          maximal charging power
        pcmax           maximal charging power
        pdmax           maximal discharging power
        =============== ===================================================================

        =============== ===================================================================
        Constraints     Documentation
        =============== ===================================================================
        _soc_init       None
        _e_balance      Energy balance constraint
        _p_init         Initialize power
        _e_min          Minimal energy constraint
        _e_max          Maximal energy constraint
        _soc_final      Final soc constraint
        _soc_min        Minimal state of charge constraint
        _soc_max        Maximal state of charge constraint
        _pmax           Power bounds constraint
        _dpdmax         Maximal varation of descharging power constraint
        _dpcmax         Maximal varation of charging power constraint
        =============== ===================================================================

        =============== ===================================================================
        Ports           Documentation
        =============== ===================================================================
        outlet          output power of the battery (kW), using source convention
        =============== ===================================================================

        =============== ===================================================================
        Expressions     Documentation
        =============== ===================================================================
        soc             Expression of the state of charge
        =============== ===================================================================

        """
        super().__init__(*args, **kwds)

        self.emin = Param(default=0, doc='minimum energy (kWh)', mutable=True, within=NonNegativeReals)
        self.emax = Param(default=UB, doc='maximal energy', mutable=True)
        self.socmin = Param(default=0, doc='minimum soc', mutable=True)
        self.pinit  = Param(default=None, doc='initial output power of the battery (default : None)', mutable=True)
        self.socmax = Param(default=100, doc='maximal soc', mutable=True)
        self.soc0 = Param(default=50, doc='initial state', mutable=True)
        self.socf = Param(default=50, doc='final state', mutable=True)
        self.dpdmax = Param(default=UB, doc='maximal discharging power', mutable=True)
        self.dpcmax = Param(default=UB, doc='maximal charging power', mutable=True)
        self.pcmax = Param(default=UB, doc='maximal charging power', mutable=True, within=NonNegativeReals)
        self.pdmax = Param(default=UB, doc='maximal discharging power', mutable=True, within=NonNegativeReals)

        def _init_e(m, t):
            if m.soc0.value is not None:
                return m.soc0 * m.emax / 100
            else:
                return 50

        self.p = Var(self.time, doc='energy derivative with respect to time', initialize=0)
        self.e = Var(self.time, doc='energy in battery', initialize=_init_e)

        self.de = DerivativeVar(self.e, wrt=self.time, initialize=0, doc='variation of energy  with respect to time')
        self.dp = DerivativeVar(self.p, wrt=self.time, initialize=0,
                                doc='variation of the battery power with respect to time',
                                bounds=lambda m, t: (-m.dpcmax, m.dpdmax))

        self.outlet = Port(initialize={'f': (self.p, Port.Extensive, {'include_splitfrac': False})},
                           doc='output power of the battery (kW), using source convention')

        # initializing pinit should not be done, since it can introduce infeasibility in case of moving horizon
        def _p_init(m, t):
            if m.pinit.value is not None:
                if t == m.time.first():
                    return m.p[t] == m.pinit
            return Constraint.Skip

        def _e_min(m, t):
            if m.emin.value is None:
                return Constraint.Skip
            return m.e[t] >= m.emin

        def _e_max(m, t):
            if m.emax.value is None:
                return Constraint.Skip
            return m.e[t] <= m.emax

        def _pmax(m, t):
            if m.pcmax.value is None:
                return Constraint.Skip
            else:
                return -m.pcmax, m.p[t], m.pdmax

        @self.Constraint(self.time, doc='initial state of charge')
        def _soc_init(m, t):
            if m.soc0.value is None:
                return Constraint.Skip
            else:
                if t == m.time.first():
                    return m.e[t] == m.soc0 * m.emax / 100
                else:
                    return Constraint.Skip

        def _soc_final(m, t):
            if m.socf.value is None:
                return Constraint.Skip
            else:
                if t == m.time.last():
                    return m.e[t] == m.socf * m.emax / 100
                else:
                    return Constraint.Skip

        def _soc_min(m, t):
            if m.socmin.value is None:
                return Constraint.Skip
            return m.e[t] >= m.socmin * m.emax / 100

        def _soc_max(m, t):
            if m.socmax.value is None:
                return Constraint.Skip
            return m.e[t] <= m.emax * m.socmax / 100

        def _dpcmax(m, t):
            if m.dpcmax.value is None:
                return Constraint.Skip
            else:
                return m.dp[t] >= -m.dpcmax

        def _dpdmax(m, t):
            if m.dpdmax.value is None:
                return Constraint.Skip
            else:
                return m.dp[t] <= m.dpdmax

        def _energy_balance(m, t):
            return m.de[t] == 1 / 3600 * m.p[t]

        self._e_balance = Constraint(self.time, rule=_energy_balance, doc='Energy balance constraint')
        self._p_init = Constraint(self.time, rule=_p_init, doc='Initialize power')
        self._e_min = Constraint(self.time, rule=_e_min, doc='Minimal energy constraint')
        self._e_max = Constraint(self.time, rule=_e_max, doc='Maximal energy constraint')
        self._soc_final = Constraint(self.time, rule=_soc_final, doc='Final soc constraint')
        self._soc_min = Constraint(self.time, rule=_soc_min, doc='Minimal state of charge constraint')
        self._soc_max = Constraint(self.time, rule=_soc_max, doc='Maximal state of charge constraint')
        self._pmax = Constraint(self.time, rule=_pmax, doc='Power bounds constraint')
        self._dpdmax = Constraint(self.time, rule=_dpdmax, doc='Maximal varation of descharging power constraint')
        self._dpcmax = Constraint(self.time, rule=_dpcmax, doc='Maximal varation of charging power constraint')

        self.soc = Expression(self.time, rule=lambda m, t: 100 * m.e[t] / m.emax,
                              doc='Expression of the state of charge')
Example #24
0
    def _fug_vap(self):
        def fug_vap_rule(b, i):
            return b.mole_frac_phase['Vap', i] * b.pressure

        self.fug_vap = Expression(self._params.component_list,
                                  rule=fug_vap_rule)
Example #25
0
def define_state(b):
    # FpcTP contains full information on the phase equilibrium, so flash
    # calculations re not always needed
    b.always_flash = False

    # Check that only necessary state_bounds are defined
    expected_keys = [
        "flow_mol_phase_comp", "enth_mol", "temperature", "pressure"
    ]
    if (b.params.config.state_bounds is not None
            and any(b.params.config.state_bounds.keys()) not in expected_keys):
        for k in b.params.config.state_bounds.keys():
            if k not in expected_keys:
                raise ConfigurationError(
                    "{} - found unexpected state_bounds key {}. Please ensure "
                    "bounds are provided only for expected state variables "
                    "and that you have typed the variable names correctly.".
                    format(b.name, k))

    units = b.params.get_metadata().derived_units
    # Get bounds and initial values from config args
    f_bounds, f_init = get_bounds_from_config(b, "flow_mol_phase_comp",
                                              units["flow_mole"])
    t_bounds, t_init = get_bounds_from_config(b, "temperature",
                                              units["temperature"])
    p_bounds, p_init = get_bounds_from_config(b, "pressure", units["pressure"])

    # Add state variables
    b.flow_mol_phase_comp = Var(b.phase_component_set,
                                initialize=f_init,
                                domain=NonNegativeReals,
                                bounds=f_bounds,
                                doc='Phase-component molar flowrate',
                                units=units["flow_mole"])
    b.pressure = Var(initialize=p_init,
                     domain=NonNegativeReals,
                     bounds=p_bounds,
                     doc='State pressure',
                     units=units["pressure"])
    b.temperature = Var(initialize=t_init,
                        domain=NonNegativeReals,
                        bounds=t_bounds,
                        doc='State temperature',
                        units=units["temperature"])

    # Add supporting variables
    b.flow_mol = Expression(expr=sum(b.flow_mol_phase_comp[i]
                                     for i in b.phase_component_set),
                            doc="Total molar flowrate")

    def flow_mol_phase(b, p):
        return sum(b.flow_mol_phase_comp[p, j] for j in b.component_list
                   if (p, j) in b.phase_component_set)

    b.flow_mol_phase = Expression(b.phase_list,
                                  rule=flow_mol_phase,
                                  doc='Phase molar flow rates')

    def rule_flow_mol_comp(b, j):
        return sum(b.flow_mol_phase_comp[p, j] for p in b.phase_list
                   if (p, j) in b.phase_component_set)

    b.flow_mol_comp = Expression(b.component_list,
                                 rule=rule_flow_mol_comp,
                                 doc='Component molar flow rates')

    def mole_frac_comp(b, j):
        return (sum(b.flow_mol_phase_comp[p, j]
                    for p in b.phase_list if (p, j) in b.phase_component_set) /
                b.flow_mol)

    b.mole_frac_comp = Expression(b.component_list,
                                  rule=mole_frac_comp,
                                  doc='Mixture mole fractions')

    b.mole_frac_phase_comp = Var(b.phase_component_set,
                                 initialize=1 / len(b.component_list),
                                 doc='Phase mole fractions',
                                 units=None)

    def rule_mole_frac_phase_comp(b, p, j):
        return b.mole_frac_phase_comp[p, j] * b.flow_mol_phase[p] == \
            b.flow_mol_phase_comp[p, j]

    b.mole_frac_phase_comp_eq = Constraint(b.phase_component_set,
                                           rule=rule_mole_frac_phase_comp)

    def rule_phase_frac(b, p):
        if len(b.phase_list) == 1:
            return 1
        else:
            return b.flow_mol_phase[p] / b.flow_mol

    b.phase_frac = Expression(b.phase_list,
                              rule=rule_phase_frac,
                              doc='Phase fractions')

    # Add electrolye state vars if required
    if b.params._electrolyte:
        define_electrolyte_state(b)

    # -------------------------------------------------------------------------
    # General Methods
    def get_material_flow_terms_FpcTP(p, j):
        """Create material flow terms for control volume."""
        return b.flow_mol_phase_comp[p, j]

    b.get_material_flow_terms = get_material_flow_terms_FpcTP

    def get_enthalpy_flow_terms_FpcTP(p):
        """Create enthalpy flow terms."""
        # enth_mol_phase probably does not exist when this is created
        # Use try/except to build flow term if not present
        try:
            eflow = b._enthalpy_flow_term
        except AttributeError:

            def rule_eflow(b, p):
                return b.flow_mol_phase[p] * b.enth_mol_phase[p]

            eflow = b._enthalpy_flow_term = Expression(b.phase_list,
                                                       rule=rule_eflow)
        return eflow[p]

    b.get_enthalpy_flow_terms = get_enthalpy_flow_terms_FpcTP

    def get_material_density_terms_FpcTP(p, j):
        """Create material density terms."""
        # dens_mol_phase probably does not exist when this is created
        # Use try/except to build term if not present
        try:
            mdens = b._material_density_term
        except AttributeError:

            def rule_mdens(b, p, j):
                return b.dens_mol_phase[p] * b.mole_frac_phase_comp[p, j]

            mdens = b._material_density_term = Expression(
                b.phase_component_set, rule=rule_mdens)
        return mdens[p, j]

    b.get_material_density_terms = get_material_density_terms_FpcTP

    def get_energy_density_terms_FpcTP(p):
        """Create energy density terms."""
        # Density and energy terms probably do not exist when this is created
        # Use try/except to build term if not present
        try:
            edens = b._energy_density_term
        except AttributeError:

            def rule_edens(b, p):
                return b.dens_mol_phase[p] * b.energy_internal_mol_phase[p]

            edens = b._energy_density_term = Expression(b.phase_list,
                                                        rule=rule_edens)
        return edens[p]

    b.get_energy_density_terms = get_energy_density_terms_FpcTP

    def default_material_balance_type_FpcTP():
        return MaterialBalanceType.componentTotal

    b.default_material_balance_type = default_material_balance_type_FpcTP

    def default_energy_balance_type_FpcTP():
        return EnergyBalanceType.enthalpyTotal

    b.default_energy_balance_type = default_energy_balance_type_FpcTP

    def get_material_flow_basis_FpcTP():
        return MaterialFlowBasis.molar

    b.get_material_flow_basis = get_material_flow_basis_FpcTP

    def define_state_vars_FpcTP():
        """Define state vars."""
        return {
            "flow_mol_phase_comp": b.flow_mol_phase_comp,
            "temperature": b.temperature,
            "pressure": b.pressure
        }

    b.define_state_vars = define_state_vars_FpcTP

    def define_display_vars_FpcTP():
        """Define display vars."""
        return {
            "Molar Flowrate": b.flow_mol_phase_comp,
            "Temperature": b.temperature,
            "Pressure": b.pressure
        }

    b.define_display_vars = define_display_vars_FpcTP
Example #26
0
    def _make_flash_eq(self):
        def rule_total_mass_balance(b):
            return b.flow_mol_phase['Liq'] + \
                b.flow_mol_phase['Vap'] == b.flow_mol

        self.total_flow_balance = Constraint(rule=rule_total_mass_balance)

        def rule_comp_mass_balance(b, i):
            return b.flow_mol * b.mole_frac[i] == \
                b.flow_mol_phase['Liq'] * b.mole_frac_phase['Liq', i] + \
                b.flow_mol_phase['Vap'] * b.mole_frac_phase['Vap', i]

        self.component_flow_balances = Constraint(self._params.component_list,
                                                  rule=rule_comp_mass_balance)

        def rule_mole_frac(b):
            return sum(b.mole_frac_phase['Liq', i]
                       for i in b._params.component_list) -\
                sum(b.mole_frac_phase['Vap', i]
                    for i in b._params.component_list) == 0

        self.sum_mole_frac = Constraint(rule=rule_mole_frac)

        if self.config.defined_state is False:
            # applied at outlet only
            self.sum_mole_frac_out = \
                Constraint(expr=1 == sum(self.mole_frac[i]
                           for i in self._params.component_list))

        if self.config.has_phase_equilibrium:
            # Definition of equilibrium temperature for smooth VLE
            self._teq = Var(initialize=self.temperature.value,
                            doc='Temperature for calculating '
                            'phase equilibrium')
            self._t1 = Var(initialize=self.temperature.value,
                           doc='Intermediate temperature for calculating Teq')

            self.eps_1 = Param(default=0.01,
                               mutable=True,
                               doc='Smoothing parameter for Teq')
            self.eps_2 = Param(default=0.0005,
                               mutable=True,
                               doc='Smoothing parameter for Teq')

            # PSE paper Eqn 13
            def rule_t1(b):
                return b._t1 == 0.5 * \
                    (b.temperature + b.temperature_bubble +
                     sqrt((b.temperature - b.temperature_bubble)**2 +
                          b.eps_1**2))

            self._t1_constraint = Constraint(rule=rule_t1)

            # PSE paper Eqn 14
            # TODO : Add option for supercritical extension
            def rule_teq(b):
                return b._teq == 0.5 * (b._t1 + b.temperature_dew - sqrt(
                    (b._t1 - b.temperature_dew)**2 + b.eps_2**2))

            self._teq_constraint = Constraint(rule=rule_teq)

            def rule_tr_eq(b, i):
                return b._teq / b._params.temperature_crit[i]

            self._tr_eq = Expression(self._params.component_list,
                                     rule=rule_tr_eq,
                                     doc='Component reduced temperatures [-]')

            def rule_equilibrium(b, i):
                return b.fug_vap[i] == b.fug_liq[i]
            self.equilibrium_constraint = \
                Constraint(self._params.component_list, rule=rule_equilibrium)
Example #27
0
def add_model_components(m, d, scenario_directory, subproblem, stage):
    """
    The following Pyomo model components are defined in this module:

    +-------------------------------------------------------------------------+
    | Sets                                                                    |
    +=========================================================================+
    | | :code:`CARBON_TAX_PRJS`                                               |
    | | *Within*: :code:`PROJECTS`                                            |
    |                                                                         |
    | Two set of carbonaceous projects we need to track for the carbon tax.   |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Required Input Params                                                   |
    +=========================================================================+
    | | :code:`carbon_tax_zone`                                               |
    | | *Defined over*: :code:`CARBON_TAX_PRJS`                               |
    | | *Within*: :code:`CARBON_TAX_ZONES`                                    |
    |                                                                         |
    | This param describes the carbon tax zone for each carbon tax project.   |
    +-------------------------------------------------------------------------+
    | | :code:`carbon_tax_allowance`                                          |
    | | *Defined over*: :code:`CARBON_TAX_PRJS`, `CARBON_TAX_PRJ_OPR_PRDS`    |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | This param describes the carbon tax allowance for each carbon tax       |
    | project.                                                                |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Derived Sets                                                            |
    +=========================================================================+
    | | :code:`CARBON_TAX_PRJS_BY_CARBON_TAX_ZONE`                            |
    | | *Defined over*: :code:`CARBON_TAX_ZONES`                              |
    | | *Within*: :code:`CARBON_TAX_PRJS`                                     |
    |                                                                         |
    | Indexed set that describes the list of carbonaceous projects for each   |
    | carbon tax zone.                                                        |
    +-------------------------------------------------------------------------+
    | | :code:`CARBON_TAX_PRJ_OPR_TMPS`                                       |
    | | *Within*: :code:`PRJ_OPR_TMPS`                                        |
    |                                                                         |
    | Two-dimensional set that defines all project-timepoint combinations     |
    | when a carbon tax project can be operational.                           |
    +-------------------------------------------------------------------------+
    | | :code:`CARBON_TAX_PRJ_OPR_PRDS`                                       |
    | | *Within*: :code:`PRJ_OPR_PRDS`                                        |
    |                                                                         |
    | Two-dimensional set that defines all project-period combinations        |
    | when a carbon tax project can be operational.                           |
    +-------------------------------------------------------------------------+

    """

    # Sets
    ###########################################################################

    m.CARBON_TAX_PRJS = Set(within=m.PROJECTS)

    m.CARBON_TAX_PRJ_OPR_TMPS = Set(
        within=m.PRJ_OPR_TMPS,
        initialize=lambda mod: [
            (p, tmp) for (p, tmp) in mod.PRJ_OPR_TMPS if p in mod.CARBON_TAX_PRJS
        ],
    )

    m.CARBON_TAX_PRJ_OPR_PRDS = Set(
        within=m.PRJ_OPR_PRDS,
        initialize=lambda mod: [
            (p, tmp) for (p, tmp) in mod.PRJ_OPR_PRDS if p in mod.CARBON_TAX_PRJS
        ],
    )

    # Input Params
    ###########################################################################

    m.carbon_tax_zone = Param(m.CARBON_TAX_PRJS, within=m.CARBON_TAX_ZONES)

    m.carbon_tax_allowance = Param(
        m.CARBON_TAX_PRJS, m.PERIODS, within=NonNegativeReals, default=0
    )

    # Derived Sets
    ###########################################################################

    m.CARBON_TAX_PRJS_BY_CARBON_TAX_ZONE = Set(
        m.CARBON_TAX_ZONES,
        within=m.CARBON_TAX_PRJS,
        initialize=lambda mod, co2_z: subset_init_by_param_value(
            mod, "CARBON_TAX_PRJS", "carbon_tax_zone", co2_z
        ),
    )

    # Expressions
    ###########################################################################

    def carbon_tax_allowance_rule(mod, prj, tmp):
        """
        Allowance from each project. Multiply by the timepoint duration,
        timepoint weight and power to get the total emissions allowance.
        """

        return (
            mod.Power_Provision_MW[prj, tmp]
            * mod.carbon_tax_allowance[prj, mod.period[tmp]]
        )

    m.Project_Carbon_Tax_Allowance = Expression(
        m.CARBON_TAX_PRJ_OPR_TMPS, rule=carbon_tax_allowance_rule
    )
Example #28
0
def generate_model(data):

    # unpack and fix the data
    cameastemp = data['Ca_meas']
    cbmeastemp = data['Cb_meas']
    ccmeastemp = data['Cc_meas']
    trmeastemp = data['Tr_meas']

    cameas={}
    cbmeas={}
    ccmeas={}
    trmeas={}
    for i in cameastemp.keys():
        cameas[float(i)] = cameastemp[i]
        cbmeas[float(i)] = cbmeastemp[i]
        ccmeas[float(i)] = ccmeastemp[i]
        trmeas[float(i)] = trmeastemp[i]

    m = ConcreteModel()

    #
    # Measurement Data
    #
    m.measT = Set(initialize=sorted(cameas.keys()))
    m.Ca_meas = Param(m.measT, initialize=cameas)
    m.Cb_meas = Param(m.measT, initialize=cbmeas)
    m.Cc_meas = Param(m.measT, initialize=ccmeas)
    m.Tr_meas = Param(m.measT, initialize=trmeas)

    #
    # Parameters for semi-batch reactor model
    #
    m.R = Param(initialize=8.314) # kJ/kmol/K
    m.Mwa = Param(initialize=50.0) # kg/kmol
    m.rhor = Param(initialize=1000.0) # kg/m^3
    m.cpr = Param(initialize=3.9) # kJ/kg/K
    m.Tf = Param(initialize=300) # K
    m.deltaH1 = Param(initialize=-40000.0) # kJ/kmol
    m.deltaH2 = Param(initialize=-50000.0) # kJ/kmol
    m.alphaj = Param(initialize=0.8) # kJ/s/m^2/K
    m.alphac = Param(initialize=0.7) # kJ/s/m^2/K
    m.Aj = Param(initialize=5.0) # m^2
    m.Ac = Param(initialize=3.0) # m^2
    m.Vj = Param(initialize=0.9) # m^3
    m.Vc = Param(initialize=0.07) # m^3
    m.rhow = Param(initialize=700.0) # kg/m^3
    m.cpw = Param(initialize=3.1) # kJ/kg/K
    m.Ca0 = Param(initialize=data['Ca0']) # kmol/m^3)
    m.Cb0 = Param(initialize=data['Cb0']) # kmol/m^3)
    m.Cc0 = Param(initialize=data['Cc0']) # kmol/m^3)
    m.Tr0 = Param(initialize=300.0) # K
    m.Vr0 = Param(initialize=1.0) # m^3

    m.time = ContinuousSet(bounds=(0, 21600), initialize=m.measT)  # Time in seconds

    #
    # Control Inputs
    #
    def _initTc(m, t):
        if t < 10800:
            return data['Tc1']
        else:
            return data['Tc2']
    m.Tc = Param(m.time, initialize=_initTc, default=_initTc)  # bounds= (288,432) Cooling coil temp, control input

    def _initFa(m, t):
        if t < 10800:
            return data['Fa1']
        else:
            return data['Fa2']
    m.Fa = Param(m.time, initialize=_initFa, default=_initFa)  # bounds=(0,0.05) Inlet flow rate, control input

    #
    # Parameters being estimated
    #
    m.k1 = Var(initialize=14, bounds=(2,100))  # 1/s Actual: 15.01
    m.k2 = Var(initialize=90, bounds=(2,150))  # 1/s Actual: 85.01
    m.E1 = Var(initialize=27000.0, bounds=(25000,40000))  # kJ/kmol Actual: 30000
    m.E2 = Var(initialize=45000.0, bounds=(35000,50000))  # kJ/kmol Actual: 40000
    # m.E1.fix(30000)
    # m.E2.fix(40000)


    #
    # Time dependent variables
    #
    m.Ca = Var(m.time, initialize=m.Ca0, bounds=(0,25))
    m.Cb = Var(m.time, initialize=m.Cb0, bounds=(0,25))
    m.Cc = Var(m.time, initialize=m.Cc0, bounds=(0,25))
    m.Vr = Var(m.time, initialize=m.Vr0)
    m.Tr = Var(m.time, initialize=m.Tr0)
    m.Tj = Var(m.time, initialize=310.0, bounds=(288,None)) # Cooling jacket temp, follows coil temp until failure

    #
    # Derivatives in the model
    #
    m.dCa = DerivativeVar(m.Ca)
    m.dCb = DerivativeVar(m.Cb)
    m.dCc = DerivativeVar(m.Cc)
    m.dVr = DerivativeVar(m.Vr)
    m.dTr = DerivativeVar(m.Tr)

    #
    # Differential Equations in the model
    #

    def _dCacon(m,t):
        if t == 0:
            return Constraint.Skip
        return m.dCa[t] == m.Fa[t]/m.Vr[t] - m.k1*exp(-m.E1/(m.R*m.Tr[t]))*m.Ca[t]
    m.dCacon = Constraint(m.time, rule=_dCacon)

    def _dCbcon(m,t):
        if t == 0:
            return Constraint.Skip
        return m.dCb[t] == m.k1*exp(-m.E1/(m.R*m.Tr[t]))*m.Ca[t] - \
                           m.k2*exp(-m.E2/(m.R*m.Tr[t]))*m.Cb[t]
    m.dCbcon = Constraint(m.time, rule=_dCbcon)

    def _dCccon(m,t):
        if t == 0:
            return Constraint.Skip
        return m.dCc[t] == m.k2*exp(-m.E2/(m.R*m.Tr[t]))*m.Cb[t]
    m.dCccon = Constraint(m.time, rule=_dCccon)

    def _dVrcon(m,t):
        if t == 0:
            return Constraint.Skip
        return m.dVr[t] == m.Fa[t]*m.Mwa/m.rhor
    m.dVrcon = Constraint(m.time, rule=_dVrcon)

    def _dTrcon(m,t):
        if t == 0:
            return Constraint.Skip
        return m.rhor*m.cpr*m.dTr[t] == \
               m.Fa[t]*m.Mwa*m.cpr/m.Vr[t]*(m.Tf-m.Tr[t]) - \
               m.k1*exp(-m.E1/(m.R*m.Tr[t]))*m.Ca[t]*m.deltaH1 - \
               m.k2*exp(-m.E2/(m.R*m.Tr[t]))*m.Cb[t]*m.deltaH2 + \
               m.alphaj*m.Aj/m.Vr0*(m.Tj[t]-m.Tr[t]) + \
               m.alphac*m.Ac/m.Vr0*(m.Tc[t]-m.Tr[t])
    m.dTrcon = Constraint(m.time, rule=_dTrcon)

    def _singlecooling(m,t):
        return m.Tc[t] == m.Tj[t]
    m.singlecooling = Constraint(m.time, rule=_singlecooling)

    # Initial Conditions
    def _initcon(m):
        yield m.Ca[m.time.first()] == m.Ca0
        yield m.Cb[m.time.first()] == m.Cb0
        yield m.Cc[m.time.first()] == m.Cc0
        yield m.Vr[m.time.first()] == m.Vr0
        yield m.Tr[m.time.first()] == m.Tr0
    m.initcon = ConstraintList(rule=_initcon)

    #
    # Stage-specific cost computations
    #
    def ComputeFirstStageCost_rule(model):
        return 0
    m.FirstStageCost = Expression(rule=ComputeFirstStageCost_rule)

    def AllMeasurements(m):
        return sum((m.Ca[t] - m.Ca_meas[t]) ** 2 + (m.Cb[t] - m.Cb_meas[t]) ** 2 
                   + (m.Cc[t] - m.Cc_meas[t]) ** 2
                   + 0.01 * (m.Tr[t] - m.Tr_meas[t]) ** 2 for t in m.measT)

    def MissingMeasurements(m):
        if data['experiment'] == 1:
            return sum((m.Ca[t] - m.Ca_meas[t]) ** 2 + (m.Cb[t] - m.Cb_meas[t]) ** 2 
                       + (m.Cc[t] - m.Cc_meas[t]) ** 2
                       + (m.Tr[t] - m.Tr_meas[t]) ** 2 for t in m.measT)
        elif data['experiment'] == 2:
            return sum((m.Tr[t] - m.Tr_meas[t]) ** 2 for t in m.measT)
        else:
            return sum((m.Cb[t] - m.Cb_meas[t]) ** 2 
                       + (m.Tr[t] - m.Tr_meas[t]) ** 2 for t in m.measT)

    m.SecondStageCost = Expression(rule=MissingMeasurements)

    def total_cost_rule(model):
        return model.FirstStageCost + model.SecondStageCost
    m.Total_Cost_Objective = Objective(rule=total_cost_rule, sense=minimize)

    # Discretize model
    disc = TransformationFactory('dae.collocation')
    disc.apply_to(m, nfe=20, ncp=4)
    return m
def generic_add_model_components(
    m,
    d,
    reserve_zone_set,
    reserve_requirement_tmp_param,
    reserve_requirement_percent_param,
    reserve_zone_load_zone_set,
    reserve_requirement_expression,
):
    """
    :param m:
    :param d:
    :param reserve_zone_set:
    :param reserve_requirement_tmp_param:
    :param reserve_requirement_percent_param:
    :param reserve_zone_load_zone_set:
    :param reserve_requirement_expression:
    :return:

    Generic treatment of reserves. This function creates model components
    related to a particular reserve requirement, including
    1) the reserve requirement by zone and timepoint, if any
    2) the reserve requirement as a percent of load and map for which load
    zones' load to consider.
    """

    # Magnitude of the requirement by reserve zone and timepoint
    # If not specified for a reserve zone - timepoint combination,
    # will default to 0
    setattr(
        m,
        reserve_requirement_tmp_param,
        Param(getattr(m, reserve_zone_set),
              m.TMPS,
              within=NonNegativeReals,
              default=0),
    )

    # Requirement as percentage of load
    setattr(
        m,
        reserve_requirement_percent_param,
        Param(getattr(m, reserve_zone_set), within=PercentFraction, default=0),
    )

    # Load zones included in the reserve percentage requirement
    setattr(
        m,
        reserve_zone_load_zone_set,
        Set(dimen=2, within=getattr(m, reserve_zone_set) * m.LOAD_ZONES),
    )

    def reserve_requirement_rule(mod, reserve_zone, tmp):
        # If we have a map of reserve zones to load zones, apply the percentage
        # target; if no map provided, the percentage_target is 0
        if getattr(mod, reserve_zone_load_zone_set):
            percentage_target = sum(
                getattr(mod, reserve_requirement_percent_param)[reserve_zone] *
                mod.static_load_mw[lz, tmp]
                for (_reserve_zone,
                     lz) in getattr(mod, reserve_zone_load_zone_set)
                if _reserve_zone == reserve_zone)
        else:
            percentage_target = 0

        return (
            getattr(mod, reserve_requirement_tmp_param)[reserve_zone, tmp] +
            percentage_target)

    setattr(
        m,
        reserve_requirement_expression,
        Expression(getattr(m, reserve_zone_set) * m.TMPS,
                   rule=reserve_requirement_rule),
    )
Example #30
0
def define_state(b):
    # FTPx formulation always requires a flash, so set flag to True
    # TODO: should have some checking to make sure developers implement this properly
    b.always_flash = True

    # Check that only necessary state_bounds are defined
    expected_keys = ["flow_mol", "enth_mol", "temperature", "pressure"]
    if (b.params.config.state_bounds is not None
            and any(b.params.config.state_bounds.keys()) not in expected_keys):
        for k in b.params.config.state_bounds.keys():
            if "mole_frac" in k:
                _log.warning("{} - found state_bounds argument for {}."
                             " Mole fraction bounds are set automatically and "
                             "this argument will be ignored.".format(
                                 b.name, k))
            elif k not in expected_keys:
                raise ConfigurationError(
                    "{} - found unexpected state_bounds key {}. Please ensure "
                    "bounds are provided only for expected state variables "
                    "and that you have typed the variable names correctly.".
                    format(b.name, k))

    units = b.params.get_metadata().derived_units
    # Get bounds and initial values from config args
    f_bounds, f_init = get_bounds_from_config(b, "flow_mol",
                                              units["flow_mole"])
    h_bounds, h_init = get_bounds_from_config(b, "enth_mol",
                                              units["energy_mole"])
    p_bounds, p_init = get_bounds_from_config(b, "pressure", units["pressure"])
    t_bounds, t_init = get_bounds_from_config(b, "temperature",
                                              units["temperature"])

    # Add state variables
    b.flow_mol = Var(initialize=f_init,
                     domain=NonNegativeReals,
                     bounds=f_bounds,
                     doc=' Total molar flowrate',
                     units=units["flow_mole"])
    b.mole_frac_comp = Var(b.component_list,
                           bounds=(0, None),
                           initialize=1 / len(b.component_list),
                           doc='Mixture mole fractions',
                           units=None)
    b.pressure = Var(initialize=p_init,
                     domain=NonNegativeReals,
                     bounds=p_bounds,
                     doc='State pressure',
                     units=units["pressure"])

    b.enth_mol = Var(initialize=h_init,
                     bounds=h_bounds,
                     doc='State molar enthalpy',
                     units=units["energy_mole"])

    # Add supporting variables
    if f_init is None:
        fp_init = None
    else:
        fp_init = f_init / len(b.phase_list)

    b.flow_mol_phase = Var(b.phase_list,
                           initialize=fp_init,
                           domain=NonNegativeReals,
                           bounds=f_bounds,
                           doc='Phase molar flow rates',
                           units=units["flow_mole"])

    b.mole_frac_phase_comp = Var(b.phase_component_set,
                                 initialize=1 / len(b.component_list),
                                 bounds=(0, None),
                                 doc='Phase mole fractions',
                                 units=None)

    def flow_mol_phase_comp_rule(b, p, j):
        return b.flow_mol_phase[p] * b.mole_frac_phase_comp[p, j]

    b.flow_mol_phase_comp = Expression(b.phase_component_set,
                                       rule=flow_mol_phase_comp_rule)

    b.temperature = Var(initialize=t_init,
                        domain=NonNegativeReals,
                        bounds=t_bounds,
                        doc='Temperature',
                        units=units["temperature"])

    b.phase_frac = Var(b.phase_list,
                       initialize=1 / len(b.phase_list),
                       bounds=(0, None),
                       doc='Phase fractions',
                       units=None)

    # Add electrolye state vars if required
    # This must occur before adding the enthalpy constraint, as it needs true
    # species mole fractions
    if b.params._electrolyte:
        define_electrolyte_state(b)

    # Add supporting constraints
    if b.config.defined_state is False:
        # applied at outlet only
        b.sum_mole_frac_out = Constraint(expr=1 == sum(
            b.mole_frac_comp[i] for i in b.component_list))

    def rule_enth_mol(b):
        return b.enth_mol == sum(b.enth_mol_phase[p] * b.phase_frac[p]
                                 for p in b.phase_list)

    b.enth_mol_eq = Constraint(rule=rule_enth_mol,
                               doc="Total molar enthalpy mixing rule")

    if len(b.phase_list) == 1:

        def rule_total_mass_balance(b):
            return b.flow_mol_phase[b.phase_list[1]] == b.flow_mol

        b.total_flow_balance = Constraint(rule=rule_total_mass_balance)

        def rule_comp_mass_balance(b, i):
            return b.mole_frac_comp[i] == \
                b.mole_frac_phase_comp[b.phase_list[1], i]

        b.component_flow_balances = Constraint(b.component_list,
                                               rule=rule_comp_mass_balance)

        def rule_phase_frac(b, p):
            return b.phase_frac[p] == 1

        b.phase_fraction_constraint = Constraint(b.phase_list,
                                                 rule=rule_phase_frac)

    elif len(b.phase_list) == 2:
        # For two phase, use Rachford-Rice formulation
        def rule_total_mass_balance(b):
            return sum(b.flow_mol_phase[p] for p in b.phase_list) == \
                b.flow_mol

        b.total_flow_balance = Constraint(rule=rule_total_mass_balance)

        def rule_comp_mass_balance(b, i):
            return b.flow_mol * b.mole_frac_comp[i] == sum(
                b.flow_mol_phase[p] * b.mole_frac_phase_comp[p, i]
                for p in b.phase_list if (p, i) in b.phase_component_set)

        b.component_flow_balances = Constraint(b.component_list,
                                               rule=rule_comp_mass_balance)

        def rule_mole_frac(b):
            return sum(b.mole_frac_phase_comp[b.phase_list[1], i]
                       for i in b.component_list
                       if (b.phase_list[1], i) in b.phase_component_set) -\
                sum(b.mole_frac_phase_comp[b.phase_list[2], i]
                    for i in b.component_list
                    if (b.phase_list[2], i) in b.phase_component_set) == 0

        b.sum_mole_frac = Constraint(rule=rule_mole_frac)

        def rule_phase_frac(b, p):
            return b.phase_frac[p] * b.flow_mol == b.flow_mol_phase[p]

        b.phase_fraction_constraint = Constraint(b.phase_list,
                                                 rule=rule_phase_frac)

    else:
        # Otherwise use a general formulation
        def rule_comp_mass_balance(b, i):
            return b.flow_mol * b.mole_frac_comp[i] == sum(
                b.flow_mol_phase[p] * b.mole_frac_phase_comp[p, i]
                for p in b.phase_list if (p, i) in b.phase_component_set)

        b.component_flow_balances = Constraint(b.component_list,
                                               rule=rule_comp_mass_balance)

        def rule_mole_frac(b, p):
            return sum(b.mole_frac_phase_comp[p, i] for i in b.component_list
                       if (p, i) in b.phase_component_set) == 1

        b.sum_mole_frac = Constraint(b.phase_list, rule=rule_mole_frac)

        def rule_phase_frac(b, p):
            return b.phase_frac[p] * b.flow_mol == b.flow_mol_phase[p]

        b.phase_fraction_constraint = Constraint(b.phase_list,
                                                 rule=rule_phase_frac)

    # -------------------------------------------------------------------------
    # General Methods
    def get_material_flow_terms_FTPx(p, j):
        """Create material flow terms for control volume."""
        return b.flow_mol_phase_comp[p, j]

    b.get_material_flow_terms = get_material_flow_terms_FTPx

    def get_enthalpy_flow_terms_FTPx(p):
        """Create enthalpy flow terms."""
        # enth_mol_phase probably does not exist when this is created
        # Use try/except to build flow term if not present
        try:
            eflow = b._enthalpy_flow_term
        except AttributeError:

            def rule_eflow(b, p):
                return b.flow_mol_phase[p] * b.enth_mol_phase[p]

            eflow = b._enthalpy_flow_term = Expression(b.phase_list,
                                                       rule=rule_eflow)
        return eflow[p]

    b.get_enthalpy_flow_terms = get_enthalpy_flow_terms_FTPx

    def get_material_density_terms_FTPx(p, j):
        """Create material density terms."""
        # dens_mol_phase probably does not exist when this is created
        # Use try/except to build term if not present
        try:
            mdens = b._material_density_term
        except AttributeError:

            def rule_mdens(b, p, j):
                return b.dens_mol_phase[p] * b.mole_frac_phase_comp[p, j]

            mdens = b._material_density_term = Expression(
                b.phase_component_set, rule=rule_mdens)
        return mdens[p, j]

    b.get_material_density_terms = get_material_density_terms_FTPx

    def get_energy_density_terms_FTPx(p):
        """Create energy density terms."""
        # Density and energy terms probably do not exist when this is created
        # Use try/except to build term if not present
        try:
            edens = b._energy_density_term
        except AttributeError:

            def rule_edens(b, p):
                return b.dens_mol_phase[p] * b.energy_internal_mol_phase[p]

            edens = b._energy_density_term = Expression(b.phase_list,
                                                        rule=rule_edens)
        return edens[p]

    b.get_energy_density_terms = get_energy_density_terms_FTPx

    def default_material_balance_type_FTPx():
        return MaterialBalanceType.componentTotal

    b.default_material_balance_type = default_material_balance_type_FTPx

    def default_energy_balance_type_FTPx():
        return EnergyBalanceType.enthalpyTotal

    b.default_energy_balance_type = default_energy_balance_type_FTPx

    def get_material_flow_basis_FTPx():
        return MaterialFlowBasis.molar

    b.get_material_flow_basis = get_material_flow_basis_FTPx

    def define_state_vars_FPhx():
        """Define state vars."""
        return {
            "flow_mol": b.flow_mol,
            "mole_frac_comp": b.mole_frac_comp,
            "enth_mol": b.enth_mol,
            "pressure": b.pressure
        }

    b.define_state_vars = define_state_vars_FPhx

    def define_display_vars_FPhx():
        """Define display vars."""
        return {
            "Total Molar Flowrate": b.flow_mol,
            "Total Mole Fraction": b.mole_frac_comp,
            "Molar Enthalpy": b.enth_mol,
            "Pressure": b.pressure
        }

    b.define_display_vars = define_display_vars_FPhx