Exemple #1
0
 def test_empty_singleton(self):
     a = Constraint()
     a.construct()
     #
     # Even though we construct a SimpleConstraint,
     # if it is not initialized that means it is "empty"
     # and we should encounter errors when trying to access the
     # _ConstraintData interface methods until we assign
     # something to the constraint.
     #
     self.assertEqual(a._constructed, True)
     self.assertEqual(len(a), 0)
     try:
         a()
         self.fail("Component is empty")
     except ValueError:
         pass
     try:
         a.body
         self.fail("Component is empty")
     except ValueError:
         pass
     try:
         a.lower
         self.fail("Component is empty")
     except ValueError:
         pass
     try:
         a.upper
         self.fail("Component is empty")
     except ValueError:
         pass
     try:
         a.equality
         self.fail("Component is empty")
     except ValueError:
         pass
     try:
         a.strict_lower
         self.fail("Component is empty")
     except ValueError:
         pass
     try:
         a.strict_upper
         self.fail("Component is empty")
     except ValueError:
         pass
     x = Var(initialize=1.0)
     x.construct()
     a.set_value((0, x, 2))
     self.assertEqual(len(a), 1)
     self.assertEqual(a(), 1)
     self.assertEqual(a.body(), 1)
     self.assertEqual(a.lower(), 0)
     self.assertEqual(a.upper(), 2)
     self.assertEqual(a.equality, False)
     self.assertEqual(a.strict_lower, False)
     self.assertEqual(a.strict_upper, False)
Exemple #2
0
 def test_unconstructed_singleton(self):
     a = Constraint()
     self.assertEqual(a._constructed, False)
     self.assertEqual(len(a), 0)
     try:
         a()
         self.fail("Component is unconstructed")
     except ValueError:
         pass
     try:
         a.body
         self.fail("Component is unconstructed")
     except ValueError:
         pass
     try:
         a.lower
         self.fail("Component is unconstructed")
     except ValueError:
         pass
     try:
         a.upper
         self.fail("Component is unconstructed")
     except ValueError:
         pass
     try:
         a.equality
         self.fail("Component is unconstructed")
     except ValueError:
         pass
     try:
         a.strict_lower
         self.fail("Component is unconstructed")
     except ValueError:
         pass
     try:
         a.strict_upper
         self.fail("Component is unconstructed")
     except ValueError:
         pass
     x = Var(initialize=1.0)
     x.construct()
     a.construct()
     a.set_value((0, x, 2))
     self.assertEqual(len(a), 1)
     self.assertEqual(a(), 1)
     self.assertEqual(a.body(), 1)
     self.assertEqual(a.lower(), 0)
     self.assertEqual(a.upper(), 2)
     self.assertEqual(a.equality, False)
     self.assertEqual(a.strict_lower, False)
     self.assertEqual(a.strict_upper, False)
Exemple #3
0
    def __init__(self,
                 cov,
                 occ,
                 groups_4digit,
                 beta,
                 t_max_allele=2,
                 solver="glpk",
                 threads=1,
                 verbosity=0):
        """
        Constructor
        """

        self.__beta = float(beta)
        self.__t_max_allele = t_max_allele
        self.__solver = SolverFactory(solver)
        self.__threads = threads
        self.__opts = {"threads": threads} if threads > 1 else {}
        self.__verbosity = verbosity
        self.__changed = True  # model needs to know if it changed from last run or not
        self.__ks = 1
        self.__groups_4digit = groups_4digit

        loci_alleles = defaultdict(list)
        for type_4digit, group_alleles in groups_4digit.iteritems():
            # print type_4digit, group_alleles
            loci_alleles[type_4digit.split('*')[0]].extend(group_alleles)

        loci = loci_alleles

        self.__allele_to_4digit = {
            allele: type_4digit
            for type_4digit, group in groups_4digit.iteritems()
            for allele in group
        }
        '''
            generates the basic ILP model
        '''

        model = ConcreteModel()

        # init Sets
        model.LociNames = Set(initialize=loci.keys())
        model.Loci = Set(model.LociNames, initialize=lambda m, l: loci[l])

        L = list(itertools.chain(*loci.values()))
        reconst = {allele_id: 0.01 for allele_id in L if '_' in allele_id}
        R = set([r for (r, _) in cov.keys()])
        model.L = Set(initialize=L)
        model.R = Set(initialize=R)

        # init Params
        model.cov = Param(model.R,
                          model.L,
                          initialize=lambda model, r, a: cov.get((r, a), 0))
        model.reconst = Param(model.L,
                              initialize=lambda model, a: reconst.get(a, 0))

        model.occ = Param(model.R, initialize=occ)
        model.t_allele = Param(initialize=self.__t_max_allele, mutable=True)

        model.beta = Param(
            initialize=self.__beta,
            validate=lambda val, model: 0.0 <= float(self.__beta) <= 0.999,
            mutable=True)
        model.nof_loci = Param(initialize=len(loci))

        # init variables
        model.x = Var(model.L, domain=Binary)
        model.y = Var(model.R, domain=Binary)

        model.re = Var(model.R, bounds=(0.0, None))
        model.hetero = Var(bounds=(0.0, model.nof_loci))

        # init objective
        model.read_cov = Objective(rule=lambda model: sum(
            model.occ[r] * (model.y[r] - model.beta * (model.re[r]))
            for r in model.R) - sum(model.reconst[a] * model.x[a]
                                    for a in model.L),
                                   sense=maximize)

        # init Constraints
        model.max_allel_selection = Constraint(
            model.LociNames,
            rule=lambda model, l: sum(model.x[a] for a in model.Loci[l]
                                      ) <= model.t_allele)
        model.min_allel_selection = Constraint(
            model.LociNames,
            rule=lambda model, l: sum(model.x[a] for a in model.Loci[l]) >= 1)
        model.is_read_cov = Constraint(
            model.R,
            rule=lambda model, r: sum(model.cov[r, a] * model.x[a]
                                      for a in model.L) >= model.y[r])
        model.heterozygot_count = Constraint(
            rule=lambda model: model.hetero >= sum(model.x[a] for a in model.L
                                                   ) - model.nof_loci)

        # regularization constraints
        model.reg1 = Constraint(
            model.R,
            rule=lambda model, r: model.re[r] <= model.nof_loci * model.y[r])
        model.reg2 = Constraint(
            model.R, rule=lambda model, r: model.re[r] <= model.hetero)
        model.reg3 = Constraint(model.R,
                                rule=lambda model, r: model.re[r] >= model.
                                hetero - model.nof_loci * (1 - model.y[r]))

        # generate constraint list for solution enumeration
        model.c = ConstraintList()
        # Generate instance. Used to be .create() but deprecated since,
        # as ConcreteModels are instances on their own now.
        self.__instance = model
Exemple #4
0
    def build(self):
        """Build the model.

        Args:
            None
        Returns:
            None
        """
        # Setup model build logger
        model_log = idaeslog.getModelLogger(self.name, tag="unit")

        # Call UnitModel.build to setup dynamics
        super(CondenserData, self).build()

        # Check config arguments
        if self.config.temperature_spec is None:
            raise ConfigurationError("temperature_spec config argument "
                                     "has not been specified. Please select "
                                     "a valid option.")
        if (self.config.condenser_type == CondenserType.partialCondenser) and \
                (self.config.temperature_spec ==
                 TemperatureSpec.atBubblePoint):
            raise ConfigurationError("condenser_type set to partial but "
                                     "temperature_spec set to atBubblePoint. "
                                     "Select customTemperature and specify "
                                     "outlet temperature.")

        # Add Control Volume for the condenser
        self.control_volume = ControlVolume0DBlock(
            default={
                "dynamic": self.config.dynamic,
                "has_holdup": self.config.has_holdup,
                "property_package": self.config.property_package,
                "property_package_args": self.config.property_package_args
            })

        self.control_volume.add_state_blocks(has_phase_equilibrium=True)

        self.control_volume.add_material_balances(
            balance_type=self.config.material_balance_type,
            has_phase_equilibrium=True)

        self.control_volume.add_energy_balances(
            balance_type=self.config.energy_balance_type,
            has_heat_transfer=True)

        self.control_volume.add_momentum_balances(
            balance_type=self.config.momentum_balance_type,
            has_pressure_change=self.config.has_pressure_change)

        # Get liquid and vapor phase objects from the property package
        # to be used below. Avoids repition.
        _liquid_list = []
        _vapor_list = []
        for p in self.config.property_package.phase_list:
            pobj = self.config.property_package.get_phase(p)
            if pobj.is_vapor_phase():
                _vapor_list.append(p)
            elif pobj.is_liquid_phase():
                _liquid_list.append(p)
            else:
                _liquid_list.append(p)
                model_log.warning(
                    "A non-liquid/non-vapor phase was detected but will "
                    "be treated as a liquid.")

        # Create a pyomo set for indexing purposes. This set is appended to
        # model otherwise results in an abstract set.
        self._liquid_set = Set(initialize=_liquid_list)
        self._vapor_set = Set(initialize=_vapor_list)

        self._make_ports()

        if self.config.condenser_type == CondenserType.totalCondenser:

            self._make_splits_total_condenser()

            if (self.config.temperature_spec == TemperatureSpec.atBubblePoint):
                # Option 1: if true, condition for total condenser
                # (T_cond = T_bubble)
                # Option 2: if this is false, then user has selected
                # custom temperature spec and needs to fix an outlet
                # temperature.
                def rule_total_cond(self, t):
                    return self.control_volume.properties_out[t].\
                        temperature == self.control_volume.properties_out[t].\
                        temperature_bubble

                self.eq_total_cond_spec = Constraint(self.flowsheet().time,
                                                     rule=rule_total_cond)

        else:
            self._make_splits_partial_condenser()

        # Add object reference to variables of the control volume
        # Reference to the heat duty
        self.heat_duty = Reference(self.control_volume.heat[:])

        # Reference to the pressure drop (if set to True)
        if self.config.has_pressure_change:
            self.deltaP = Reference(self.control_volume.deltaP[:])
Exemple #5
0
m.BatteryCharge = Var(m.TIMEPOINTS, initialize = m.total_start_capacity)

# *NEW* vehicle storage capacity factor
#m.vehicle_cf = m.total_start_capac / m.total_max_capacity

#####################
#Constraints
#generated power + curtailed power serves load
def ServeLoadConstraint_rule(m, t):
    return (
        sum(m.DispatchGen[g, t] for g in m.GENERATORS) + m.DispatchCurtail[t]
        ==
        (m.nominal_load[t] + m.DispatchLoad[t] + m.ChargeCurtail[t])
    )
m.ServeLoadConstraint = Constraint(
    m.TIMEPOINTS, rule=ServeLoadConstraint_rule
)

# dispatched generated power must be less than what exists
def MaxOutputConstraint_rule(m, g, t):
    return (
        m.DispatchGen[g, t] <= m.BuildGen[g] * m.max_cf[g, t]
    )
m.MaxOutputConstraint = Constraint(
    m.GENERATORS, m.TIMEPOINTS, rule=MaxOutputConstraint_rule
)

# emitted CO2 must be less than what's allowed
def LimitCO2_rule(m):
    return (m.co2_total_tons <= m.co2_baseline_tons * m.co2_limit_vs_baseline)
m.LimitCO2 = Constraint(rule=LimitCO2_rule)
Exemple #6
0
    def test_findComponentOn_nestedTuples(self):
        # Tests for #1069
        m = ConcreteModel()
        m.x = Var()
        m.c = Constraint(Any)
        m.c[0] = m.x >= 0
        m.c[(1, )] = m.x >= 1
        m.c[(2, )] = m.x >= 2
        m.c[2] = m.x >= 3
        self.assertIs(ComponentUID(m.c[0]).find_component_on(m), m.c[0])
        self.assertIs(ComponentUID('c[0]').find_component_on(m), m.c[0])
        self.assertIsNone(ComponentUID('c[(0,)]').find_component_on(m))
        self.assertIs(
            ComponentUID(m.c[(1, )]).find_component_on(m), m.c[(1, )])
        self.assertIs(ComponentUID('c[(1,)]').find_component_on(m), m.c[(1, )])
        self.assertIsNone(ComponentUID('c[1]').find_component_on(m))
        self.assertIs(ComponentUID('c[(2,)]').find_component_on(m), m.c[(2, )])
        self.assertIs(ComponentUID('c[2]').find_component_on(m), m.c[2])
        self.assertEqual(len(m.c), 4)

        self.assertEqual(repr(ComponentUID(m.c[0])), "c[0]")
        self.assertEqual(repr(ComponentUID(m.c[(1, )])), "c[(1,)]")
        self.assertEqual(str(ComponentUID(m.c[0])), "c[0]")
        self.assertEqual(str(ComponentUID(m.c[(1, )])), "c[(1,)]")

        m = ConcreteModel()
        m.x = Var()
        m.c = Constraint([0, 1])
        m.c[0] = m.x >= 0
        m.c[(1, )] = m.x >= 1
        self.assertIs(ComponentUID(m.c[0]).find_component_on(m), m.c[0])
        self.assertIs(ComponentUID(m.c[(0, )]).find_component_on(m), m.c[0])
        self.assertIs(ComponentUID('c[0]').find_component_on(m), m.c[0])
        self.assertIs(ComponentUID('c[(0,)]').find_component_on(m), m.c[0])
        self.assertIs(ComponentUID(m.c[1]).find_component_on(m), m.c[1])
        self.assertIs(ComponentUID(m.c[(1, )]).find_component_on(m), m.c[1])
        self.assertIs(ComponentUID('c[(1,)]').find_component_on(m), m.c[1])
        self.assertIs(ComponentUID('c[1]').find_component_on(m), m.c[1])
        self.assertEqual(len(m.c), 2)

        m = ConcreteModel()
        m.b = Block(Any)
        m.b[0].c = Block(Any)
        m.b[0].c[0].x = Var()
        m.b[(1, )].c = Block(Any)
        m.b[(1, )].c[(1, )].x = Var()
        ref = m.b[0].c[0].x
        self.assertIs(ComponentUID(ref).find_component_on(m), ref)
        ref = 'm.b[0].c[(0,)].x'
        self.assertIsNone(ComponentUID(ref).find_component_on(m))
        ref = m.b[(1, )].c[(1, )].x
        self.assertIs(ComponentUID(ref).find_component_on(m), ref)
        ref = 'm.b[(1,)].c[1].x'
        self.assertIsNone(ComponentUID(ref).find_component_on(m))

        buf = {}
        ref = m.b[0].c[0].x
        self.assertIs(
            ComponentUID(ref, cuid_buffer=buf).find_component_on(m), ref)
        self.assertEqual(len(buf), 3)
        ref = 'm.b[0].c[(0,)].x'
        self.assertIsNone(
            ComponentUID(ref, cuid_buffer=buf).find_component_on(m))
        self.assertEqual(len(buf), 3)
        ref = m.b[(1, )].c[(1, )].x
        self.assertIs(
            ComponentUID(ref, cuid_buffer=buf).find_component_on(m), ref)
        self.assertEqual(len(buf), 4)
        ref = 'm.b[(1,)].c[1].x'
        self.assertIsNone(
            ComponentUID(ref, cuid_buffer=buf).find_component_on(m))
        self.assertEqual(len(buf), 4)
Exemple #7
0
    def _make_performance(self):
        """
        Constraints for unit model.

        Args: None

        Returns: None

        """

        # ======================================================================
        # Custom Sets
        vap_comp = self.config.vapor_side.property_package.component_list
        liq_comp = self.config.liquid_side.property_package.component_list
        equilibrium_comp = vap_comp & liq_comp
        solvent_comp_list = \
            self.config.liquid_side.property_package.solvent_set
        solute_comp_list = self.config.liquid_side.property_package.solute_set
        vapor_phase_list_ref = \
            self.config.vapor_side.property_package.phase_list
        liquid_phase_list_ref = \
            self.config.liquid_side.property_package.phase_list

        # Packing  parameters
        self.eps_ref = Param(initialize=0.97,units=None,
                             mutable=True,
                             doc="Packing void space m3/m3")

        self.packing_specific_area = Param(initialize=250,units=pyunits.m**2 / pyunits.m**3,
                           mutable=True,
                           doc="Packing specific surface area (m2/m3)")
        
        self.packing_channel_size = Param(initialize=0.1,units=pyunits.m,
                           mutable=True,
                           doc="Packing channel size (m)")
        
        self.hydraulic_diameter = Expression(expr=4 * self.eps_ref / self.packing_specific_area,
                                 doc="Hydraulic diameter (m)")

        # Add the integer indices along vapor phase length domain
        self.zi = Param(self.vapor_phase.length_domain, mutable=True,
                        doc='''Integer indexing parameter required for transfer
                             across boundaries of a given volume element''')
                             
        # Set the integer indices along vapor phase length domain
        for i, x in enumerate(self.vapor_phase.length_domain, 1):
            self.zi[x] = i

        # Unit Model Design Variables
        # Geometry
        self.diameter_column = Var(domain=Reals,
                                   initialize=0.1,
                                   units=pyunits.m,
                                   doc='Column diameter')
        
        self.area_column = Var(domain=Reals,
                               initialize=0.5,
                               units=pyunits.m**2,
                               doc='Column cross-sectional area')
        
        self.length_column = Var(domain=Reals,
                                 initialize=4.9,
                                 units=pyunits.m,
                                 doc='Column length')

        # Hydrodynamics
        self.velocity_vap = Var(self.flowsheet().time,
                                self.vapor_phase.length_domain,
                                domain=NonNegativeReals,
                                initialize=2,
                                units=pyunits.m / pyunits.s,
                                doc='Vapor superficial velocity')
        
        self.velocity_liq = Var(self.flowsheet().time,
                                self.liquid_phase.length_domain,
                                domain=NonNegativeReals,
                                initialize=0.01,
                                units=pyunits.m / pyunits.s,
                                doc='Liquid superficial velocity')
        
        self.holdup_liq = Var(self.flowsheet().time,
                              self.liquid_phase.length_domain,
                              initialize=0.001,
                              doc='Volumetric liquid holdup [-]')

        def rule_holdup_vap(blk, t, x):
            return blk.eps_ref - blk.holdup_liq[t, x]

        self.holdup_vap = Expression(self.flowsheet().time,
                                     self.vapor_phase.length_domain,
                                     rule=rule_holdup_vap,
                                     doc='Volumetric vapor holdup [-]')
        
        # Define gas velocity at flooding point (m/s)                
        self.gas_velocity_flood = Var(self.flowsheet().time,
                                self.vapor_phase.length_domain,
                                initialize=1,
                                doc='Gas velocity at flooding point')
            
        # Flooding fraction 
        def rule_flood_fraction(blk, t, x):
            return blk.velocity_vap[t, x]/blk.gas_velocity_flood[t, x]
            
        self.flood_fraction = Expression(self.flowsheet().time,
                                    self.vapor_phase.length_domain,
                                    rule=rule_flood_fraction,
                                    doc='Flooding fraction (expected to be below 0.8)')
        
        # Mass and heat transfer terms
        
        # Mass transfer terms
        self.pressure_equil = Var(
            self.flowsheet().time,
            self.vapor_phase.length_domain,
            equilibrium_comp,
            domain=NonNegativeReals,
            initialize=500,
            units=pyunits.Pa,
            doc='Equilibruim pressure of diffusing components at interface')
        
        self.interphase_mass_transfer = Var(
            self.flowsheet().time,
            self.liquid_phase.length_domain,
            equilibrium_comp,
            domain=Reals,
            initialize=0.1,
            units=pyunits.mol / (pyunits.s * pyunits.m),
            doc='Rate at which moles of diffusing species transfered into liquid')
        
        self.enhancement_factor = Var(self.flowsheet().time,
                                      self.liquid_phase.length_domain,
                                      units=None,
                                      initialize=160,
                                      doc='Enhancement factor')

        # Heat transfer terms
        self.heat_flux_vap = Var(self.flowsheet().time,
                            self.vapor_phase.length_domain,
                            domain=Reals,
                            initialize=0.0,
                            units=pyunits.J / (pyunits.s * (pyunits.m**3)),
                            doc='Volumetric heat flux in vapor phase')

        # =====================================================================
        # Add performance equations

        # Inter-facial Area model ([m2/m3]):

        self.area_interfacial = Var(self.flowsheet().time,
                                    self.vapor_phase.length_domain,
                                    initialize=0.9,
                                    doc='Specific inter-facial area')

        # ---------------------------------------------------------------------
        # Geometry constraints

        # Column area [m2]
        @self.Constraint(doc="Column cross-sectional area")
        def column_cross_section_area(blk):
            return blk.area_column == (
                CONST.pi * 0.25 * (blk.diameter_column)**2)

        # Area of control volume : vapor side and liquid side
        control_volume_area_definition = ''' column_area * phase_holdup.
        The void fraction of the vapor phase (volumetric vapor holdup) and that
        of the liquid phase(volumetric liquid holdup) are
        lumped into the definition of the cross-sectional area of the
        vapor-side and liquid-side control volume respectively. Hence, the
        cross-sectional area of the control volume changes with time and space.
        '''

        if self.config.dynamic:
            @self.Constraint(self.flowsheet().time,
                             self.vapor_phase.length_domain,
                             doc=control_volume_area_definition)
            def vapor_side_area(bk, t, x):
                return bk.vapor_phase.area[t, x] == (
                    bk.area_column * bk.holdup_vap[t, x])

            @self.Constraint(self.flowsheet().time,
                             self.liquid_phase.length_domain,
                             doc=control_volume_area_definition)
            def liquid_side_area(bk, t, x):
                return bk.liquid_phase.area[t, x] == (
                    bk.area_column * bk.holdup_liq[t, x])
        else:
            self.vapor_phase.area.fix(value(self.area_column))
            self.liquid_phase.area.fix(value(self.area_column))

        # Pressure consistency in phases
        @self.Constraint(self.flowsheet().time,
                         self.liquid_phase.length_domain,
                         doc='''Mechanical equilibruim: vapor-side pressure
                                    equal liquid -side pressure''')
        def mechanical_equil(bk, t, x):
            return bk.liquid_phase.properties[t, x].pressure == \
                    bk.vapor_phase.properties[t, x].pressure

        # Length of control volume : vapor side and liquid side
        @self.Constraint(doc="Vapor side length")
        def vapor_side_length(blk):
            return blk.vapor_phase.length == blk.length_column

        @self.Constraint(doc="Liquid side length")
        def liquid_side_length(blk):
            return blk.liquid_phase.length == blk.length_column

        # ---------------------------------------------------------------------
        # Hydrodynamic constraints
        # Vapor superficial velocity

        @self.Constraint(self.flowsheet().time,
                         self.vapor_phase.length_domain,
                         doc="Vapor superficial velocity")
        def eq_velocity_vap(blk, t, x):
            return blk.velocity_vap[t, x] * blk.area_column * \
                blk.vapor_phase.properties[t, x].dens_mol == \
                blk.vapor_phase.properties[t, x].flow_mol

        # Liquid superficial velocity
        @self.Constraint(self.flowsheet().time,
                         self.liquid_phase.length_domain,
                         doc="Liquid superficial velocity")
        def eq_velocity_liq(blk, t, x):
            return blk.velocity_liq[t, x] * blk.area_column * \
                blk.liquid_phase.properties[t, x].dens_mol == \
                blk.liquid_phase.properties[t, x].flow_mol

        # ---------------------------------------------------------------------
        # Mass transfer coefficients
        # Mass transfer coefficients of diffusing components in vapor phase [mol/m2.s.Pa]
        self.k_v = Var(self.flowsheet().time,
                       self.vapor_phase.length_domain,
                       equilibrium_comp,
                       doc=' Vapor phase mass transfer coefficient')

        # Mass transfer coefficients of diffusing components in liquid phase  [m/s]
        self.k_l = Var(self.flowsheet().time,
                       self.liquid_phase.length_domain,
                       equilibrium_comp,
                       doc='Liquid phase mass transfer coefficient')

        # Intermediate term
        def rule_phi(blk, t, x, j):
            if x == self.vapor_phase.length_domain.first():
                return Expression.Skip
            else:
                zb = self.vapor_phase.length_domain.at(self.zi[x].value - 1)
                return (blk.enhancement_factor[t, zb] *
                        blk.k_l[t, zb, j] /
                        blk.k_v[t, x, j])

        self.phi = Expression(
            self.flowsheet().time,
            self.vapor_phase.length_domain,
            solute_comp_list,
            rule=rule_phi,
            doc='Equilibruim partial pressure intermediate term for solute')

        # Equilibruim partial pressure of diffusing components at interface
        @self.Constraint(self.flowsheet().time,
                          self.vapor_phase.length_domain,
                          equilibrium_comp,
                          doc='''Equilibruim partial pressure of diffusing
                                components at interface''')
        def pressure_at_interface(blk, t, x, j):
            if x == self.vapor_phase.length_domain.first():
                return blk.pressure_equil[t, x, j] == 0.0
            else:
                zb = self.vapor_phase.length_domain.at(self.zi[x].value - 1)
                lprops = blk.liquid_phase.properties[t, zb]
                henrycomp = lprops.params.get_component(j).config.henry_component
                if henrycomp is not None and "Liq" in henrycomp:
                    return blk.pressure_equil[t, x, j] == (
                        (blk.vapor_phase.properties[t, x].mole_frac_comp[j] *
                          blk.vapor_phase.properties[
                              t, x].pressure + blk.phi[t, x, j] *
                          lprops.conc_mol_phase_comp_true['Liq',j]) /
                        (1 + blk.phi[t, x, j] /
                          blk.liquid_phase.properties[t, zb].henry['Liq',j]))
                else:
                    return blk.pressure_equil[t, x, j] == (
                        lprops.vol_mol_phase['Liq'] *
                        lprops.conc_mol_phase_comp_true['Liq',j] *
                        lprops.pressure_sat_comp[j])

        # Mass transfer of  diffusing components in vapor phase
        def rule_mass_transfer(blk, t, x, j):
            if x == self.vapor_phase.length_domain.first():
                return blk.interphase_mass_transfer[t, x, j] == 0.0
            else:
                return blk.interphase_mass_transfer[t, x, j] == (
                blk.k_v[t, x, j] *
                blk.area_interfacial[t, x] * blk.area_column *
                (blk.vapor_phase.properties[t, x].mole_frac_comp[j] *
                 blk.vapor_phase.properties[t, x].pressure -
                 blk.pressure_equil[t, x, j]))

        self.mass_transfer_vapor = Constraint(self.flowsheet().time,
                                        self.vapor_phase.length_domain,
                                        equilibrium_comp,
                                        rule=rule_mass_transfer,
                                        doc="mass transfer in vapor phase")

        # Liquid phase mass transfer handle
        @self.Constraint(self.flowsheet().time,
                          self.liquid_phase.length_domain,
                          self.liquid_phase.properties.phase_component_set,
                          doc="mass transfer to liquid")
        def liquid_phase_mass_transfer_handle(blk, t, x, p, j):
            if x == self.liquid_phase.length_domain.last():
                return blk.liquid_phase.mass_transfer_term[t, x, p, j] == 0.0
            else:
                zf = self.liquid_phase.length_domain.at(self.zi[x].value + 1)
                if j in equilibrium_comp:
                    return blk.liquid_phase.mass_transfer_term[t, x, p, j] == \
                        blk.interphase_mass_transfer[t, zf, j]
                else:
                    return blk.liquid_phase.mass_transfer_term[t, x, p, j] == \
                        0.0

        # Vapor phase mass transfer handle
        @self.Constraint(self.flowsheet().time,
                         self.vapor_phase.length_domain,
                         self.vapor_phase.properties.phase_component_set,
                         doc="mass transfer from vapor")
        def vapor_phase_mass_transfer_handle(blk, t, x, p, j):
            if x == self.vapor_phase.length_domain.first():
                return blk.vapor_phase.mass_transfer_term[t, x, p, j] == 0.0
            else:
                if j in equilibrium_comp:
                    return blk.vapor_phase.mass_transfer_term[t, x, p, j] == \
                        -blk.interphase_mass_transfer[t, x, j]
                else:
                    return blk.vapor_phase.mass_transfer_term[t, x, p, j] == \
                        0.0

        # Heat transfer coefficients
        # Vapor-liquid heat transfer coefficient [J/m2.s.K]

        self.h_v = Var(self.flowsheet().time,
                       self.vapor_phase.length_domain,
                       initialize=100,
                       doc='''Vapor-liquid heat transfer coefficient''')

        # Vapor-liquid heat transfer coeff modified by Ackmann factor [J/m.s.K]
        def rule_heat_transfer_coeff_Ack(blk, t, x):
            if x == self.vapor_phase.length_domain.first():
                return Expression.Skip
            else:
                Ackmann_factor =\
                    sum(blk.vapor_phase.properties[t, x].cp_mol_phase_comp['Vap',j] *
                     blk.interphase_mass_transfer[t, x, j] for j in equilibrium_comp)
                return Ackmann_factor /\
                    (1 - exp(-Ackmann_factor /
                             (blk.h_v[t, x] * blk.area_interfacial[t, x] *
                              blk.area_column)))
        self.h_v_Ack = Expression(
            self.flowsheet().time,
            self.vapor_phase.length_domain,
            rule=rule_heat_transfer_coeff_Ack,
            doc='Vap-Liq heat transfer coefficient corrected by Ackmann factor')

        # Heat flux  - vapor side [J/s.m]
        @self.Constraint(self.flowsheet().time,
                         self.vapor_phase.length_domain,
                         doc="heat transfer - vapor side ")
        def vapor_phase_volumetric_heat_flux(blk, t, x):
            if x == self.vapor_phase.length_domain.first():
                return blk.heat_flux_vap[t, x] == 0
            else:
                zb = self.vapor_phase.length_domain.at(value(self.zi[x]) - 1)
                return blk.heat_flux_vap[t, x] == blk.h_v_Ack[t, x] * \
                    (blk.liquid_phase.properties[t, zb].temperature -
                      blk.vapor_phase.properties[t, x].temperature)
                    
        # Heat transfer - vapor side [J/s.m]
        @self.Constraint(self.flowsheet().time,
                         self.vapor_phase.length_domain,
                         doc="heat transfer - vapor side ")
        def vapor_phase_heat_transfer(blk, t, x):
            if x == self.vapor_phase.length_domain.first():
                return blk.vapor_phase.heat[t, x] == 0
            else:
                zb = self.vapor_phase.length_domain.at(value(self.zi[x]) - 1)
                return blk.vapor_phase.heat[t, x] == -blk.heat_flux_vap[t, x] - \
                    (sum(blk.vapor_phase.properties[t, x].enth_mol_phase_comp['Vap',j] *
                      blk.vapor_phase.mass_transfer_term[t, x, 'Vap', j] for j in solute_comp_list)) + \
                    (sum(blk.liquid_phase.properties[t, zb].enth_mol_phase_comp['Liq',j] *
                      blk.liquid_phase.mass_transfer_term[t, zb, 'Liq', j] for j in solvent_comp_list))

        # Heat transfer - liquid side [J/s.m]
        @self.Constraint(self.flowsheet().time,
                          self.liquid_phase.length_domain,
                          doc="heat transfer - liquid side ")
        def liquid_phase_heat_transfer(blk, t, x):
            if x == self.liquid_phase.length_domain.last():
                return blk.liquid_phase.heat[t, x] == 0
            else:
                zf = self.vapor_phase.length_domain.at(value(self.zi[x]) + 1)
                return blk.liquid_phase.heat[t, x] == -blk.vapor_phase.heat[t, zf]
Exemple #8
0
def add_model_components(m, d, scenario_directory, subproblem, stage):
    """
    The following Pyomo model components are defined in this module:

    +-------------------------------------------------------------------------+
    | Sets                                                                    |
    +=========================================================================+
    | | :code:`CRB_TX_LINES`                                                  |
    |                                                                         |
    | The set of carbonaceous transmission lines, i.e. transmission lines     |
    | whose imports or exports should be accounted for in the carbon cap      |
    | calculations.                                                           |
    +-------------------------------------------------------------------------+
    | | :code:`CRB_TX_OPR_TMPS`                                               |
    |                                                                         |
    | Two-dimensional set of carbonaceous transmission lines and their        |
    | operational timepoints.                                                 |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Required Input Params                                                   |
    +=========================================================================+
    | | :code:`tx_carbon_cap_zone`                                            |
    | | *Defined over*: :code:`CRB_TX_LINES`                                  |
    | | *Within*: :code:`CARBON_CAP_ZONES`                                    |
    |                                                                         |
    | The transmission line's carbon cap zone. The imports or exports for     |
    | that transmission line will count towards that zone's carbon cap.       |
    +-------------------------------------------------------------------------+
    | | :code:`carbon_cap_zone_import_direction`                              |
    | | *Defined over*: :code:`CRB_TX_LINES`                                  |
    | | *Within*: :code:`["positive", "negative"]`                            |
    |                                                                         |
    | The transmission line's import direction: "positive" ("negative")       |
    | indicates positive (negative) line flows are flows into the carbon cap  |
    | zone while negative (positive) line flows are flows out of the carbon   |
    | zone.                                                                   |
    +-------------------------------------------------------------------------+
    | | :code:`tx_co2_intensity_tons_per_mwh`                                 |
    | | *Defined over*: :code:`CRB_TX_LINES`                                  |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The transmission line's CO2-intensity in metric tonnes per MWh. This    |
    | param indicates how much emissions are added towards the carbon cap for |
    | every MWh transmitted into the carbon cap zone.                         |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Derived Sets                                                            |
    +=========================================================================+
    | | :code:`CRB_TX_LINES_BY_CARBON_CAP_ZONE`                               |
    | | *Defined over*: :code:`CARBON_CAP_ZONES`                              |
    | | *Within*: :code:`CRB_TX_LINES`                                        |
    |                                                                         |
    | Indexed set that describes the carbonaceous transmission lines          |
    | associated with each carbon cap zone.                                   |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Variables                                                               |
    +=========================================================================+
    | | :code:`Import_Carbon_Emissions_Tons`                                  |
    | | *Defined over*: :code:`CRB_TX_OPR_TMPS`                               |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | Describes the amount of imported carbon emissions for each              |
    | carbonaceous transmission in each operational timepoint.                |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Constraints                                                             |
    +=========================================================================+
    | | :code:`Carbon_Emissions_Imports_Constraint`                           |
    | | *Enforced over*: :code:`CRB_TX_OPR_TMPS`                              |
    |                                                                         |
    | Constrains the amount of imported carbon emissions for each             |
    | carbonaceous transmission in each operational timepoint, based on the   |
    | :code:`tx_co2_intensity_tons_per_mwh` param and the transmitted power.  |
    +-------------------------------------------------------------------------+

    """

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

    m.CRB_TX_LINES = Set(within=m.TX_LINES)

    m.CRB_TX_OPR_TMPS = Set(
        within=m.TX_OPR_TMPS,
        initialize=lambda mod: [(tx, tmp) for (tx, tmp) in mod.TX_OPR_TMPS
                                if tx in mod.CRB_TX_LINES])

    # Required Input Params
    ###########################################################################

    m.tx_carbon_cap_zone = Param(m.CRB_TX_LINES, within=m.CARBON_CAP_ZONES)

    m.carbon_cap_zone_import_direction = Param(m.CRB_TX_LINES,
                                               within=["positive", "negative"])

    m.tx_co2_intensity_tons_per_mwh = Param(m.CRB_TX_LINES,
                                            within=NonNegativeReals)

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

    m.CRB_TX_LINES_BY_CARBON_CAP_ZONE = Set(
        m.CARBON_CAP_ZONES,
        within=m.CRB_TX_LINES,
        initialize=lambda mod, co2_z:
        [tx for tx in mod.CRB_TX_LINES if mod.tx_carbon_cap_zone[tx] == co2_z])

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

    m.Import_Carbon_Emissions_Tons = Var(m.CRB_TX_OPR_TMPS,
                                         within=NonNegativeReals)

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

    m.Carbon_Emissions_Imports_Constraint = Constraint(
        m.CRB_TX_OPR_TMPS, rule=carbon_emissions_imports_rule)
Exemple #9
0
#  rights in this software.
#  This software is distributed under the 3-clause BSD License.
#  ___________________________________________________________________________

from pyomo.environ import ConcreteModel, Var, Objective, Constraint, log, log10, sin, cos, tan, sinh, cosh, tanh, asin, acos, atan, asinh, acosh, atanh, exp, sqrt, ceil, floor
from math import e, pi

model = ConcreteModel()

# pick a value in the domain of all of these functions
model.ONE = Var(initialize=1)
model.ZERO = Var(initialize=0)

model.obj = Objective(expr=model.ONE + model.ZERO)

model.c_log = Constraint(expr=log(model.ONE) == 0)
model.c_log10 = Constraint(expr=log10(model.ONE) == 0)

model.c_sin = Constraint(expr=sin(model.ZERO) == 0)
model.c_cos = Constraint(expr=cos(model.ZERO) == 1)
model.c_tan = Constraint(expr=tan(model.ZERO) == 0)

model.c_sinh = Constraint(expr=sinh(model.ZERO) == 0)
model.c_cosh = Constraint(expr=cosh(model.ZERO) == 1)
model.c_tanh = Constraint(expr=tanh(model.ZERO) == 0)

model.c_asin = Constraint(expr=asin(model.ZERO) == 0)
model.c_acos = Constraint(expr=acos(model.ZERO) == pi / 2)
model.c_atan = Constraint(expr=atan(model.ZERO) == 0)

model.c_asinh = Constraint(expr=asinh(model.ZERO) == 0)
def test_number_active_variables_in_deactivated_blocks(m):
    assert number_active_variables_in_deactivated_blocks(m) == 0

    m.c = Constraint(expr=m.b1.v1 >= 2)

    assert number_active_variables_in_deactivated_blocks(m) == 1
def test_active_variables_in_deactivated_blocks_set(m):
    assert len(active_variables_in_deactivated_blocks_set(m)) == 0

    m.c = Constraint(expr=m.b1.v1 >= 2)

    assert len(active_variables_in_deactivated_blocks_set(m)) == 1
def test_number_fixed_variables_only_in_inequalities(m):
    m.v3 = Var(initialize=1)
    m.v3.fix(1)
    m.c3 = Constraint(expr=m.v3 >= 1)
    assert number_fixed_variables_only_in_inequalities(m) == 1
def test_variables_only_in_inequalities(m):
    m.v3 = Var(initialize=1)
    m.c3 = Constraint(expr=m.v3 >= 1)
    assert len(variables_only_in_inequalities(m)) == 1
Exemple #14
0

# Variables
def fb(m, i):
    return 0, Weights[i]


m.x = Var(m.I, domain=NonNegativeReals, bounds=fb)

m.y = Var(m.I, domain=Binary)

# Objective Function
m.obj = Objective(expr=sum(Costs[i] * Weights[i] * m.y[i] for i in m.I))

# Production Constraints
m.c1 = Constraint(expr=sum(Cs[0][i] / 100 * m.x[i] for i in m.I) >= 65)

m.c2 = Constraint(expr=sum(Cs[1][i] / 100 * m.x[i] for i in m.I) == 18)

m.c3 = Constraint(expr=sum(Cs[2][i] / 100 * m.x[i] for i in m.I) == 10)

m.c4 = Constraint(expr=sum(Cs[3][i] / 100 * m.x[i] for i in m.I) <= 1)

# Overall production
m.c5 = Constraint(expr=sum(m.x[i] for i in m.I) == 100)


# Bound implication
def ImplyBound(m, i):
    return m.x[i] <= Weights[i] * m.y[i]
Exemple #15
0
    def _create(self, group=None):
        """
        Create constraints for GenericCAESBlock.

        Parameters
        ----------
        group : list
            List containing `.GenericCAES` objects.
            e.g. groups=[gcaes1, gcaes2,..]
        """
        m = self.parent_block()

        if group is None:
            return None

        self.GENERICCAES = Set(initialize=[n for n in group])

        # Compression: Binary variable for operation status
        self.cmp_st = Var(self.GENERICCAES, m.TIMESTEPS, within=Binary)

        # Compression: Realized capacity
        self.cmp_p = Var(self.GENERICCAES,
                         m.TIMESTEPS,
                         within=NonNegativeReals)

        # Compression: Max. Capacity
        self.cmp_p_max = Var(self.GENERICCAES,
                             m.TIMESTEPS,
                             within=NonNegativeReals)

        # Compression: Heat flow
        self.cmp_q_out_sum = Var(self.GENERICCAES,
                                 m.TIMESTEPS,
                                 within=NonNegativeReals)

        # Compression: Waste heat
        self.cmp_q_waste = Var(self.GENERICCAES,
                               m.TIMESTEPS,
                               within=NonNegativeReals)

        # Expansion: Binary variable for operation status
        self.exp_st = Var(self.GENERICCAES, m.TIMESTEPS, within=Binary)

        # Expansion: Realized capacity
        self.exp_p = Var(self.GENERICCAES,
                         m.TIMESTEPS,
                         within=NonNegativeReals)

        # Expansion: Max. Capacity
        self.exp_p_max = Var(self.GENERICCAES,
                             m.TIMESTEPS,
                             within=NonNegativeReals)

        # Expansion: Heat flow of natural gas co-firing
        self.exp_q_in_sum = Var(self.GENERICCAES,
                                m.TIMESTEPS,
                                within=NonNegativeReals)

        # Expansion: Heat flow of natural gas co-firing
        self.exp_q_fuel_in = Var(self.GENERICCAES,
                                 m.TIMESTEPS,
                                 within=NonNegativeReals)

        # Expansion: Heat flow of additional firing
        self.exp_q_add_in = Var(self.GENERICCAES,
                                m.TIMESTEPS,
                                within=NonNegativeReals)

        # Cavern: Filling levelh
        self.cav_level = Var(self.GENERICCAES,
                             m.TIMESTEPS,
                             within=NonNegativeReals)

        # Cavern: Energy inflow
        self.cav_e_in = Var(self.GENERICCAES,
                            m.TIMESTEPS,
                            within=NonNegativeReals)

        # Cavern: Energy outflow
        self.cav_e_out = Var(self.GENERICCAES,
                             m.TIMESTEPS,
                             within=NonNegativeReals)

        # TES: Filling levelh
        self.tes_level = Var(self.GENERICCAES,
                             m.TIMESTEPS,
                             within=NonNegativeReals)

        # TES: Energy inflow
        self.tes_e_in = Var(self.GENERICCAES,
                            m.TIMESTEPS,
                            within=NonNegativeReals)

        # TES: Energy outflow
        self.tes_e_out = Var(self.GENERICCAES,
                             m.TIMESTEPS,
                             within=NonNegativeReals)

        # Spot market: Positive capacity
        self.exp_p_spot = Var(self.GENERICCAES,
                              m.TIMESTEPS,
                              within=NonNegativeReals)

        # Spot market: Negative capacity
        self.cmp_p_spot = Var(self.GENERICCAES,
                              m.TIMESTEPS,
                              within=NonNegativeReals)

        # Compression: Capacity on markets
        def cmp_p_constr_rule(block, n, t):
            expr = 0
            expr += -self.cmp_p[n, t]
            expr += m.flow[list(n.electrical_input.keys())[0], n, t]
            return expr == 0

        self.cmp_p_constr = Constraint(self.GENERICCAES,
                                       m.TIMESTEPS,
                                       rule=cmp_p_constr_rule)

        # Compression: Max. capacity depending on cavern filling level
        def cmp_p_max_constr_rule(block, n, t):
            if t != 0:
                return (self.cmp_p_max[n, t] == n.params['cmp_p_max_m'] *
                        self.cav_level[n, t - 1] + n.params['cmp_p_max_b'])
            else:
                return (self.cmp_p_max[n, t] == n.params['cmp_p_max_b'])

        self.cmp_p_max_constr = Constraint(self.GENERICCAES,
                                           m.TIMESTEPS,
                                           rule=cmp_p_max_constr_rule)

        def cmp_p_max_area_constr_rule(block, n, t):
            return (self.cmp_p[n, t] <= self.cmp_p_max[n, t])

        self.cmp_p_max_area_constr = Constraint(
            self.GENERICCAES, m.TIMESTEPS, rule=cmp_p_max_area_constr_rule)

        # Compression: Status of operation (on/off)
        def cmp_st_p_min_constr_rule(block, n, t):
            return (self.cmp_p[n, t] >=
                    n.params['cmp_p_min'] * self.cmp_st[n, t])

        self.cmp_st_p_min_constr = Constraint(self.GENERICCAES,
                                              m.TIMESTEPS,
                                              rule=cmp_st_p_min_constr_rule)

        def cmp_st_p_max_constr_rule(block, n, t):
            return (self.cmp_p[n, t] <=
                    (n.params['cmp_p_max_m'] * n.params['cav_level_max'] +
                     n.params['cmp_p_max_b']) * self.cmp_st[n, t])

        self.cmp_st_p_max_constr = Constraint(self.GENERICCAES,
                                              m.TIMESTEPS,
                                              rule=cmp_st_p_max_constr_rule)

        # Compression: Heat flow out
        def cmp_q_out_constr_rule(block, n, t):
            return (self.cmp_q_out_sum[n, t] == n.params['cmp_q_out_m'] *
                    self.cmp_p[n, t] +
                    n.params['cmp_q_out_b'] * self.cmp_st[n, t])

        self.cmp_q_out_constr = Constraint(self.GENERICCAES,
                                           m.TIMESTEPS,
                                           rule=cmp_q_out_constr_rule)

        # Compression: Definition of single heat flows
        def cmp_q_out_sum_constr_rule(block, n, t):
            return (self.cmp_q_out_sum[n, t] == self.cmp_q_waste[n, t] +
                    self.tes_e_in[n, t])

        self.cmp_q_out_sum_constr = Constraint(self.GENERICCAES,
                                               m.TIMESTEPS,
                                               rule=cmp_q_out_sum_constr_rule)

        # Compression: Heat flow out ratio
        def cmp_q_out_shr_constr_rule(block, n, t):
            return (self.cmp_q_waste[n, t] *
                    n.params['cmp_q_tes_share'] == self.tes_e_in[n, t] *
                    (1 - n.params['cmp_q_tes_share']))

        self.cmp_q_out_shr_constr = Constraint(self.GENERICCAES,
                                               m.TIMESTEPS,
                                               rule=cmp_q_out_shr_constr_rule)

        # Expansion: Capacity on markets
        def exp_p_constr_rule(block, n, t):
            expr = 0
            expr += -self.exp_p[n, t]
            expr += m.flow[n, list(n.electrical_output.keys())[0], t]
            return expr == 0

        self.exp_p_constr = Constraint(self.GENERICCAES,
                                       m.TIMESTEPS,
                                       rule=exp_p_constr_rule)

        # Expansion: Max. capacity depending on cavern filling level
        def exp_p_max_constr_rule(block, n, t):
            if t != 0:
                return (self.exp_p_max[n, t] == n.params['exp_p_max_m'] *
                        self.cav_level[n, t - 1] + n.params['exp_p_max_b'])
            else:
                return (self.exp_p_max[n, t] == n.params['exp_p_max_b'])

        self.exp_p_max_constr = Constraint(self.GENERICCAES,
                                           m.TIMESTEPS,
                                           rule=exp_p_max_constr_rule)

        def exp_p_max_area_constr_rule(block, n, t):
            return (self.exp_p[n, t] <= self.exp_p_max[n, t])

        self.exp_p_max_area_constr = Constraint(
            self.GENERICCAES, m.TIMESTEPS, rule=exp_p_max_area_constr_rule)

        # Expansion: Status of operation (on/off)
        def exp_st_p_min_constr_rule(block, n, t):
            return (self.exp_p[n, t] >=
                    n.params['exp_p_min'] * self.exp_st[n, t])

        self.exp_st_p_min_constr = Constraint(self.GENERICCAES,
                                              m.TIMESTEPS,
                                              rule=exp_st_p_min_constr_rule)

        def exp_st_p_max_constr_rule(block, n, t):
            return (self.exp_p[n, t] <=
                    (n.params['exp_p_max_m'] * n.params['cav_level_max'] +
                     n.params['exp_p_max_b']) * self.exp_st[n, t])

        self.exp_st_p_max_constr = Constraint(self.GENERICCAES,
                                              m.TIMESTEPS,
                                              rule=exp_st_p_max_constr_rule)

        # Expansion: Heat flow in
        def exp_q_in_constr_rule(block, n, t):
            return (self.exp_q_in_sum[n, t] == n.params['exp_q_in_m'] *
                    self.exp_p[n, t] +
                    n.params['exp_q_in_b'] * self.exp_st[n, t])

        self.exp_q_in_constr = Constraint(self.GENERICCAES,
                                          m.TIMESTEPS,
                                          rule=exp_q_in_constr_rule)

        # Expansion: Fuel allocation
        def exp_q_fuel_constr_rule(block, n, t):
            expr = 0
            expr += -self.exp_q_fuel_in[n, t]
            expr += m.flow[list(n.fuel_input.keys())[0], n, t]
            return expr == 0

        self.exp_q_fuel_constr = Constraint(self.GENERICCAES,
                                            m.TIMESTEPS,
                                            rule=exp_q_fuel_constr_rule)

        # Expansion: Definition of single heat flows
        def exp_q_in_sum_constr_rule(block, n, t):
            return (self.exp_q_in_sum[n, t] == self.exp_q_fuel_in[n, t] +
                    self.tes_e_out[n, t] + self.exp_q_add_in[n, t])

        self.exp_q_in_sum_constr = Constraint(self.GENERICCAES,
                                              m.TIMESTEPS,
                                              rule=exp_q_in_sum_constr_rule)

        # Expansion: Heat flow in ratio
        def exp_q_in_shr_constr_rule(block, n, t):
            return (
                n.params['exp_q_tes_share'] *
                self.exp_q_fuel_in[n, t] == (1 - n.params['exp_q_tes_share']) *
                (self.exp_q_add_in[n, t] + self.tes_e_out[n, t]))

        self.exp_q_in_shr_constr = Constraint(self.GENERICCAES,
                                              m.TIMESTEPS,
                                              rule=exp_q_in_shr_constr_rule)

        # Cavern: Energy inflow
        def cav_e_in_constr_rule(block, n, t):
            return (
                self.cav_e_in[n,
                              t] == n.params['cav_e_in_m'] * self.cmp_p[n, t] +
                n.params['cav_e_in_b'])

        self.cav_e_in_constr = Constraint(self.GENERICCAES,
                                          m.TIMESTEPS,
                                          rule=cav_e_in_constr_rule)

        # Cavern: Energy outflow
        def cav_e_out_constr_rule(block, n, t):
            return (self.cav_e_out[n, t] == n.params['cav_e_out_m'] *
                    self.exp_p[n, t] + n.params['cav_e_out_b'])

        self.cav_e_out_constr = Constraint(self.GENERICCAES,
                                           m.TIMESTEPS,
                                           rule=cav_e_out_constr_rule)

        # Cavern: Storage balance
        def cav_eta_constr_rule(block, n, t):
            if t != 0:
                return (n.params['cav_eta_temp'] *
                        self.cav_level[n, t] == self.cav_level[n, t - 1] +
                        m.timeincrement[t] *
                        (self.cav_e_in[n, t] - self.cav_e_out[n, t]))
            else:
                return (n.params['cav_eta_temp'] *
                        self.cav_level[n, t] == m.timeincrement[t] *
                        (self.cav_e_in[n, t] - self.cav_e_out[n, t]))

        self.cav_eta_constr = Constraint(self.GENERICCAES,
                                         m.TIMESTEPS,
                                         rule=cav_eta_constr_rule)

        # Cavern: Upper bound
        def cav_ub_constr_rule(block, n, t):
            return (self.cav_level[n, t] <= n.params['cav_level_max'])

        self.cav_ub_constr = Constraint(self.GENERICCAES,
                                        m.TIMESTEPS,
                                        rule=cav_ub_constr_rule)

        # TES: Storage balance
        def tes_eta_constr_rule(block, n, t):
            if t != 0:
                return (self.tes_level[n, t] == self.tes_level[n, t - 1] +
                        m.timeincrement[t] *
                        (self.tes_e_in[n, t] - self.tes_e_out[n, t]))
            else:
                return (self.tes_level[n, t] == m.timeincrement[t] *
                        (self.tes_e_in[n, t] - self.tes_e_out[n, t]))

        self.tes_eta_constr = Constraint(self.GENERICCAES,
                                         m.TIMESTEPS,
                                         rule=tes_eta_constr_rule)

        # TES: Upper bound
        def tes_ub_constr_rule(block, n, t):
            return (self.tes_level[n, t] <= n.params['tes_level_max'])

        self.tes_ub_constr = Constraint(self.GENERICCAES,
                                        m.TIMESTEPS,
                                        rule=tes_ub_constr_rule)
Exemple #16
0
    def _create(self, group=None):
        """ Creates the linear constraint for the class:`ElectricalLine`
        block.

        Parameters
        ----------
        group : list
            List of oemof.solph.ElectricalLine (eline) objects for which
            the linear relation of inputs and outputs is created
            e.g. group = [eline1, eline2, ...]. The components inside the
            list need to hold a attribute `reactance` of type Sequence
            containing the reactance of the line.
        """
        if group is None:
            return None

        m = self.parent_block()

        I = {n: n.input for n in group}
        O = {n: n.output for n in group}

        # create voltage angle variables
        self.ELECTRICAL_BUSES = Set(
            initialize=[n for n in m.es.nodes if isinstance(n, ElectricalBus)])

        def _voltage_angle_bounds(block, b, t):
            return b.v_min, b.v_max

        self.voltage_angle = Var(self.ELECTRICAL_BUSES,
                                 m.TIMESTEPS,
                                 bounds=_voltage_angle_bounds)

        if True not in [b.slack for b in self.ELECTRICAL_BUSES]:
            # TODO: Make this robust to select the same slack bus for
            # the same problems
            bus = [b for b in self.ELECTRICAL_BUSES][0]
            logging.info(
                "No slack bus set,setting bus {0} as slack bus".format(
                    bus.label))
            bus.slack = True

        def _voltage_angle_relation(block):
            for t in m.TIMESTEPS:
                for n in group:
                    if O[n].slack is True:
                        self.voltage_angle[O[n], t].value = 0
                        self.voltage_angle[O[n], t].fix()
                    try:
                        lhs = m.flow[n, O[n], t]
                        rhs = 1 / n.reactance[t] * (
                            self.voltage_angle[I[n], t] -
                            self.voltage_angle[O[n], t])
                    except:
                        raise ValueError("Error in constraint creation",
                                         "of node {}".format(n.label))
                    block.electrical_flow.add((n, t), (lhs == rhs))
                    # add constraint to set in-outflow equal
                    block._equate_electrical_flows.add(
                        (n, t), (m.flow[n, O[n], t] == m.flow[I[n], n, t]))

        self.electrical_flow = Constraint(group, noruleinit=True)

        self._equate_electrical_flows = Constraint(group, noruleinit=True)

        self.electrical_flow_build = BuildAction(rule=_voltage_angle_relation)
def test_getattr_raise_exception(m):
    with pytest.raises(Exception):
        m.p.cons = Constraint(expr=m.p.raise_exception == 1)
Exemple #18
0
def initialization_tester(m, dof=0, unit=None, **init_kwargs):
    """
    A method to test initialization methods on IDAES models. This method is
    designed to be used as part of the tests for most models.

    This method checks that the initialization methods runs as expceted
    and that the state of the model (active/deactive and fixed/unfixed)
    remains the same.

    This method also add some dummy constraitns to the model and deactivates
    them to make sure that the initialization does not affect their status.

    Args:
        m: a Concrete mdoel which contains a flowsheet and a model named unit
            (i.e. m.fs.unit) which will be initialized
        dof: expected degrees of freedom during initialization, default=0
        unit: unit object to test, if None assume m.fs.unit, default='None'
        init_kwargs: model specific arguments to pass to initialize method
                     (e.g. initial guesses for states)

    Returns:
        None

    Raises:
        AssertionErrors if an issue is found
    """
    if unit is None:
        unit = m.fs.unit
    # Add some extra constraints and deactivate them to make sure
    # they remain deactivated
    # Test both indexed and unindexed constraints
    unit.__dummy_var = Var()
    unit.__dummy_equality = Constraint(expr=unit.__dummy_var == 5)
    unit.__dummy_inequality = Constraint(expr=unit.__dummy_var <= 10)

    def deq_idx(b, i):
        return unit.__dummy_var == 5
    unit.__dummy_equality_idx = Constraint([1], rule=deq_idx)

    def dieq_idx(b, i):
        return unit.__dummy_var <= 10
    unit.__dummy_inequality_idx = Constraint([1], rule=dieq_idx)

    unit.__dummy_equality.deactivate()
    unit.__dummy_inequality.deactivate()
    unit.__dummy_equality_idx[1].deactivate()
    unit.__dummy_inequality_idx[1].deactivate()

    orig_fixed_vars = fixed_variables_set(m)
    orig_act_consts = activated_constraints_set(m)

    unit.initialize(**init_kwargs)

    print(degrees_of_freedom(m))
    assert degrees_of_freedom(m) == dof

    fin_fixed_vars = fixed_variables_set(m)
    fin_act_consts = activated_constraints_set(m)

    assert len(fin_act_consts) == len(orig_act_consts)
    assert len(fin_fixed_vars) == len(orig_fixed_vars)

    for c in fin_act_consts:
        assert c in orig_act_consts
    for v in fin_fixed_vars:
        assert v in orig_fixed_vars

    # Check dummy constraints and clean up
    assert not unit.__dummy_equality.active
    assert not unit.__dummy_inequality.active
    assert not unit.__dummy_equality_idx[1].active
    assert not unit.__dummy_inequality_idx[1].active

    unit.del_component(unit.__dummy_inequality)
    unit.del_component(unit.__dummy_equality)
    unit.del_component(unit.__dummy_inequality_idx)
    unit.del_component(unit.__dummy_equality_idx)
    unit.del_component(unit.__dummy_var)
Exemple #19
0
model.x = Var(within=Boolean)
model.y = Var()
model.slackbool = Var(within=Boolean)

model.a = Param()
model.b = Param()
model.c = Param()
model.M = Param()


def y_is_x_rule(model):
    return model.y == model.x


model.y_is_x = Constraint(rule=y_is_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 _recursion1(self):
     self.recursive_cons1 = Constraint(expr=self.recursion2 == 1)
Exemple #21
0
    def test_generate_cuid_string_map(self):
        model = Block(concrete=True)
        model.x = Var()
        model.y = Var([1, 2])
        model.V = Var([('a', 'b'), (1, '2'), (3, 4)])
        model.b = Block(concrete=True)
        model.b.z = Var([1, '2'])
        setattr(model.b, '.H', Var(['a', 2]))
        model.B = Block(['a', 2], concrete=True)
        setattr(model.B['a'], '.k', Var())
        model.B[2].b = Block()
        model.B[2].b.x = Var()
        model.add_component('c tuple', Constraint(Any))
        model.component('c tuple')[(1, )] = model.x >= 0

        cuids = (
            ComponentUID.generate_cuid_string_map(model, repr_version=1),
            ComponentUID.generate_cuid_string_map(model),
        )
        self.assertEqual(len(cuids[0]), 29)
        self.assertEqual(len(cuids[1]), 29)
        for obj in [
                model, model.x, model.y, model.y_index, model.y[1], model.y[2],
                model.V, model.V_index, model.V['a', 'b'], model.V[1, '2'],
                model.V[3, 4], model.b, model.b.z, model.b.z_index,
                model.b.z[1], model.b.z['2'],
                getattr(model.b, '.H'),
                getattr(model.b, '.H_index'),
                getattr(model.b, '.H')['a'],
                getattr(model.b,
                        '.H')[2], model.B, model.B_index, model.B['a'],
                getattr(model.B['a'],
                        '.k'), model.B[2], model.B[2].b, model.B[2].b.x,
                model.component('c tuple')[(1, )]
        ]:
            self.assertEqual(ComponentUID(obj).get_repr(1), cuids[0][obj])
            self.assertEqual(repr(ComponentUID(obj)), cuids[1][obj])

        cuids = (
            ComponentUID.generate_cuid_string_map(model,
                                                  descend_into=False,
                                                  repr_version=1),
            ComponentUID.generate_cuid_string_map(model, descend_into=False),
        )
        self.assertEqual(len(cuids[0]), 18)
        self.assertEqual(len(cuids[1]), 18)
        for obj in [
                model, model.x, model.y, model.y_index, model.y[1], model.y[2],
                model.V, model.V_index, model.V['a', 'b'], model.V[1, '2'],
                model.V[3, 4], model.b, model.B, model.B_index, model.B['a'],
                model.B[2],
                model.component('c tuple')[(1, )]
        ]:
            self.assertEqual(ComponentUID(obj).get_repr(1), cuids[0][obj])
            self.assertEqual(repr(ComponentUID(obj)), cuids[1][obj])

        cuids = (
            ComponentUID.generate_cuid_string_map(model,
                                                  ctype=Var,
                                                  repr_version=1),
            ComponentUID.generate_cuid_string_map(model, ctype=Var),
        )
        self.assertEqual(len(cuids[0]), 22)
        self.assertEqual(len(cuids[1]), 22)
        for obj in [
                model, model.x, model.y, model.y[1], model.y[2], model.V,
                model.V['a', 'b'], model.V[1, '2'], model.V[3, 4], model.b,
                model.b.z, model.b.z[1], model.b.z['2'],
                getattr(model.b, '.H'),
                getattr(model.b, '.H')['a'],
                getattr(model.b, '.H')[2], model.B, model.B['a'],
                getattr(model.B['a'],
                        '.k'), model.B[2], model.B[2].b, model.B[2].b.x
        ]:
            self.assertEqual(ComponentUID(obj).get_repr(1), cuids[0][obj])
            self.assertEqual(repr(ComponentUID(obj)), cuids[1][obj])

        cuids = (
            ComponentUID.generate_cuid_string_map(model,
                                                  ctype=Var,
                                                  descend_into=False,
                                                  repr_version=1),
            ComponentUID.generate_cuid_string_map(model,
                                                  ctype=Var,
                                                  descend_into=False),
        )
        self.assertEqual(len(cuids[0]), 9)
        self.assertEqual(len(cuids[1]), 9)
        for obj in [
                model, model.x, model.y, model.y[1], model.y[2], model.V,
                model.V['a', 'b'], model.V[1, '2'], model.V[3, 4]
        ]:
            self.assertEqual(ComponentUID(obj).get_repr(1), cuids[0][obj])
            self.assertEqual(repr(ComponentUID(obj)), cuids[1][obj])
 def _recursion2(self):
     self.recursive_cons2 = Constraint(expr=self.recursion1 == 1)
Exemple #23
0
def add_model_components(m, d, scenario_directory, subproblem, stage):
    """
    The following Pyomo model components are defined in this module:

    +-------------------------------------------------------------------------+
    | Sets                                                                    |
    +=========================================================================+
    | | :code:`AVL_BIN`                                                       |
    |                                                                         |
    | The set of projects of the :code:`binary` availability type.            |
    +-------------------------------------------------------------------------+
    | | :code:`AVL_BIN_OPR_PRDS`                                              |
    |                                                                         |
    | Two-dimensional set with projects of the :code:`binary` availability    |
    | type and their operational periods.                                     |
    +-------------------------------------------------------------------------+
    | | :code:`AVL_BIN_OPR_TMPS`                                              |
    |                                                                         |
    | Two-dimensional set with projects of the :code:`binary` availability    |
    | type and their operational timepoints.                                  |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Required Input Params                                                   |
    +=========================================================================+
    | | :code:`avl_bin_unavl_hrs_per_prd`                                     |
    | | *Defined over*: :code:`AVL_BIN`                                       |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The number of hours the project must be unavailable per period.         |
    +-------------------------------------------------------------------------+
    | | :code:`avl_bin_min_unavl_hrs_per_event`                               |
    | | *Defined over*: :code:`AVL_BIN`                                       |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The minimum number of hours an unavailability event should last for.    |
    +-------------------------------------------------------------------------+
    | | :code:`avl_bin_min_avl_hrs_between_events`                            |
    | | *Defined over*: :code:`AVL_BIN`                                       |
    | | *Within*: :code:`NonNegativeReals`                                    |
    |                                                                         |
    | The minimum number of hours a project should be available between       |
    | unavailability events.                                                  |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Variables                                                               |
    +=========================================================================+
    | | :code:`AvlBin_Unavailable`                                            |
    | | *Defined over*: :code:`AVL_BIN_OPR_TMPS`                              |
    | | *Within*: :code:`Binary`                                              |
    |                                                                         |
    | Binary decision variable that specifies whether the project is          |
    | unavailable or not in each operational timepoint (1=unavailable).       |
    +-------------------------------------------------------------------------+
    | | :code:`AvlBin_Start_Unavailability`                                   |
    | | *Defined over*: :code:`AVL_BIN_OPR_TMPS`                              |
    | | *Within*: :code:`Binary`                                              |
    |                                                                         |
    | Binary decision variable that designates the start of an unavailability |
    | event (when the project goes from available to unavailable.             |
    +-------------------------------------------------------------------------+
    | | :code:`AvlBin_Stop_Unavailability`                                    |
    | | *Defined over*: :code:`AVL_BIN_OPR_TMPS`                              |
    | | *Within*: :code:`Binary`                                              |
    |                                                                         |
    | Binary decision variable that designates the end of an unavailability   |
    | event (when the project goes from unavailable to available.             |
    +-------------------------------------------------------------------------+

    |

    +-------------------------------------------------------------------------+
    | Constraints                                                             |
    +=========================================================================+
    | | :code:`AvlBin_Tot_Sched_Unavl_per_Prd_Constraint`                     |
    | | *Defined over*: :code:`AVL_BIN_OPR_PRDS`                              |
    |                                                                         |
    | The project must be unavailable for :code:`avl_bin_unavl_hrs_per_prd`   |
    | hours in each period.                                                   |
    +-------------------------------------------------------------------------+
    | | :code:`AvlBin_Unavl_Start_and_Stop_Constraint`                        |
    | | *Defined over*: :code:`AVL_BIN_OPR_TMPS`                              |
    |                                                                         |
    | Link the three binary variables in each timepoint such that             |
    | :code:`AvlBin_Start_Unavailability` is 1 if the project goes from       |
    | available to unavailable, and :code:`AvlBin_Stop_Unavailability` is 1   |
    | if the project goes from unavailable to available.                      |
    +-------------------------------------------------------------------------+
    | | :code:`AvlBin_Min_Event_Duration_Constraint`                          |
    | | *Defined over*: :code:`AVL_BIN_OPR_TMPS`                              |
    |                                                                         |
    | The duration of each unavailability event should be larger than or      |
    | equal to :code:`avl_bin_min_unavl_hrs_per_event` hours.                 |
    +-------------------------------------------------------------------------+
    | | :code:`AvlBin_Min_Time_Between_Events_Constraint`                     |
    | | *Defined over*: :code:`AVL_BIN_OPR_TMPS`                              |
    |                                                                         |
    | The time between unavailability events should be larger than or equal   |
    | to :code:`avl_bin_min_avl_hrs_between_events` hours.                    |
    +-------------------------------------------------------------------------+

    """

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

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

    m.AVL_BIN_OPR_PRDS = Set(
        dimen=2,
        within=m.PRJ_OPR_PRDS,
        initialize=lambda mod:
        list(set(
            (g, tmp) for (g, tmp) in mod.PRJ_OPR_PRDS if g in mod.AVL_BIN)),
    )

    # TODO: factor out this lambda rule, as it is used in all operational type
    #  modules and availability type modules
    m.AVL_BIN_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.AVL_BIN)),
    )

    # Required Input Params
    ###########################################################################

    m.avl_bin_unavl_hrs_per_prd = Param(m.AVL_BIN, within=NonNegativeReals)

    m.avl_bin_min_unavl_hrs_per_event = Param(m.AVL_BIN,
                                              within=NonNegativeReals)

    m.avl_bin_min_avl_hrs_between_events = Param(m.AVL_BIN,
                                                 within=NonNegativeReals)

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

    m.AvlBin_Unavailable = Var(m.AVL_BIN_OPR_TMPS, within=Binary)

    m.AvlBin_Start_Unavailability = Var(m.AVL_BIN_OPR_TMPS, within=Binary)

    m.AvlBin_Stop_Unavailability = Var(m.AVL_BIN_OPR_TMPS, within=Binary)

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

    m.AvlBin_Tot_Sched_Unavl_per_Prd_Constraint = Constraint(
        m.AVL_BIN_OPR_PRDS, rule=total_scheduled_availability_per_period_rule)

    m.AvlBin_Unavl_Start_and_Stop_Constraint = Constraint(
        m.AVL_BIN_OPR_TMPS, rule=unavailability_start_and_stop_rule)

    m.AvlBin_Min_Event_Duration_Constraint = Constraint(
        m.AVL_BIN_OPR_TMPS, rule=event_min_duration_rule)

    m.AvlBin_Min_Time_Between_Events_Constraint = Constraint(
        m.AVL_BIN_OPR_TMPS, rule=min_time_between_events_rule)
def test_getattr_protected(m):
    with pytest.raises(PropertyPackageError):
        # Call a protected component that does not exist
        m.p.cons = Constraint(expr=m.p._foo == 1)
def farmer_lp(solver="cplex"):
    # concrete model
    instance = ConcreteModel(name="Farmer_LP")

    # set
    instance.plants = ("wheat", "corn", "beet")
    instance.action = ("buy", "sell")
    instance.price = ("high", "low")

    # decision variables
    instance.area = Var(instance.plants, within=NonNegativeReals)
    instance.wheat_act = Var(instance.action, within=NonNegativeReals)
    instance.corn_act = Var(instance.action, within=NonNegativeReals)
    instance.beet_price = Var(instance.price,
                              bounds=(0, 6000),
                              within=NonNegativeReals)

    # constraint
    def area_rule(model):
        return sum(model.area[pdx] for pdx in model.plants) <= 500

    instance.area_constraint = Constraint(rule=area_rule)

    # constraint
    def min_wheat_rule(model):
        return (2.5 * model.area['wheat'] + model.wheat_act['buy'] -
                model.wheat_act['sell'] >= 200)

    instance.min_wheat_constraint = Constraint(rule=min_wheat_rule)

    # constraint
    def min_corn_rule(model):
        return (3 * model.area['corn'] + model.corn_act['buy'] -
                model.corn_act['sell'] >= 240)

    instance.min_corn_constraint = Constraint(rule=min_corn_rule)

    # constraint
    def beet_price_rule(model):
        return (model.beet_price['high'] + model.beet_price['low'] <=
                20 * model.area['beet'])

    instance.beat_price_constraint = Constraint(rule=beet_price_rule)

    # objective
    def min_cost_rule(model):
        grow_cost = (150 * model.area['wheat'] + 230 * model.area['corn'] +
                     260 * model.area['beet'])
        wheat_cost = (238 * model.wheat_act['buy'] -
                      170 * model.wheat_act['sell'])
        corn_cost = (210 * model.corn_act['buy'] -
                     150 * model.corn_act['sell'])
        beet_cost = -(36 * model.beet_price['high'] +
                      10 * model.beet_price['low'])
        return grow_cost + wheat_cost + corn_cost + beet_cost

    instance.min_cost_objective = Objective(rule=min_cost_rule, sense=minimize)
    # solve
    opt = SolverFactory(solver)
    results = opt.solve(instance)
    instance.solutions.load_from(results)
    display(instance)
    print("LP objective: {}".format(-instance.min_cost_objective()))
def test_getattr_recursion(m):
    with pytest.raises(PropertyPackageError):
        # Call a component that triggers a recursive loop of calls
        m.p.cons = Constraint(expr=m.p.recursion1 == 1)
Exemple #27
0
class CondenserData(UnitModelBlockData):
    """
    Condenser unit for distillation model.
    Unit model to condense (total/partial) the vapor from the top tray of
    the distillation column.
    """
    CONFIG = UnitModelBlockData.CONFIG()
    CONFIG.declare(
        "condenser_type",
        ConfigValue(
            default=CondenserType.totalCondenser,
            domain=In(CondenserType),
            description="Type of condenser flag",
            doc="""Indicates what type of condenser should be constructed,
**default** - CondenserType.totalCondenser.
**Valid values:** {
**CondenserType.totalCondenser** - Incoming vapor from top tray is condensed
to all liquid,
**CondenserType.partialCondenser** - Incoming vapor from top tray is
partially condensed to a vapor and liquid stream.}"""))
    CONFIG.declare(
        "temperature_spec",
        ConfigValue(default=None,
                    domain=In(TemperatureSpec),
                    description="Temperature spec for the condenser",
                    doc="""Temperature specification for the condenser,
**default** - TemperatureSpec.none
**Valid values:** {
**TemperatureSpec.none** - No spec is selected,
**TemperatureSpec.atBubblePoint** - Condenser temperature set at
bubble point i.e. total condenser,
**TemperatureSpec.customTemperature** - Condenser temperature at
user specified temperature.}"""))
    CONFIG.declare(
        "material_balance_type",
        ConfigValue(
            default=MaterialBalanceType.useDefault,
            domain=In(MaterialBalanceType),
            description="Material balance construction flag",
            doc="""Indicates what type of mass balance should be constructed,
**default** - MaterialBalanceType.componentPhase.
**Valid values:** {
**MaterialBalanceType.none** - exclude material balances,
**MaterialBalanceType.componentPhase** - use phase component balances,
**MaterialBalanceType.componentTotal** - use total component balances,
**MaterialBalanceType.elementTotal** - use total element balances,
**MaterialBalanceType.total** - use total material balance.}"""))
    CONFIG.declare(
        "energy_balance_type",
        ConfigValue(
            default=EnergyBalanceType.useDefault,
            domain=In(EnergyBalanceType),
            description="Energy balance construction flag",
            doc="""Indicates what type of energy balance should be constructed,
**default** - EnergyBalanceType.enthalpyTotal.
**Valid values:** {
**EnergyBalanceType.none** - exclude energy balances,
**EnergyBalanceType.enthalpyTotal** - single enthalpy balance for material,
**EnergyBalanceType.enthalpyPhase** - enthalpy balances for each phase,
**EnergyBalanceType.energyTotal** - single energy balance for material,
**EnergyBalanceType.energyPhase** - energy balances for each phase.}"""))
    CONFIG.declare(
        "momentum_balance_type",
        ConfigValue(
            default=MomentumBalanceType.pressureTotal,
            domain=In(MomentumBalanceType),
            description="Momentum balance construction flag",
            doc="""Indicates what type of momentum balance should be constructed,
**default** - MomentumBalanceType.pressureTotal.
**Valid values:** {
**MomentumBalanceType.none** - exclude momentum balances,
**MomentumBalanceType.pressureTotal** - single pressure balance for material,
**MomentumBalanceType.pressurePhase** - pressure balances for each phase,
**MomentumBalanceType.momentumTotal** - single momentum balance for material,
**MomentumBalanceType.momentumPhase** - momentum balances for each phase.}"""))
    CONFIG.declare(
        "has_pressure_change",
        ConfigValue(
            default=False,
            domain=In([True, False]),
            description="Pressure change term construction flag",
            doc="""Indicates whether terms for pressure change should be
constructed,
**default** - False.
**Valid values:** {
**True** - include pressure change terms,
**False** - exclude pressure change terms.}"""))
    CONFIG.declare(
        "property_package",
        ConfigValue(
            default=useDefault,
            domain=is_physical_parameter_block,
            description="Property package to use for control volume",
            doc=
            """Property parameter object used to define property calculations,
**default** - useDefault.
**Valid values:** {
**useDefault** - use default package from parent model or flowsheet,
**PropertyParameterObject** - a PropertyParameterBlock object.}"""))
    CONFIG.declare(
        "property_package_args",
        ConfigBlock(
            implicit=True,
            description="Arguments to use for constructing property packages",
            doc=
            """A ConfigBlock with arguments to be passed to a property block(s)
and used when constructing these,
**default** - None.
**Valid values:** {
see property package for documentation.}"""))

    def build(self):
        """Build the model.

        Args:
            None
        Returns:
            None
        """
        # Setup model build logger
        model_log = idaeslog.getModelLogger(self.name, tag="unit")

        # Call UnitModel.build to setup dynamics
        super(CondenserData, self).build()

        # Check config arguments
        if self.config.temperature_spec is None:
            raise ConfigurationError("temperature_spec config argument "
                                     "has not been specified. Please select "
                                     "a valid option.")
        if (self.config.condenser_type == CondenserType.partialCondenser) and \
                (self.config.temperature_spec ==
                 TemperatureSpec.atBubblePoint):
            raise ConfigurationError("condenser_type set to partial but "
                                     "temperature_spec set to atBubblePoint. "
                                     "Select customTemperature and specify "
                                     "outlet temperature.")

        # Add Control Volume for the condenser
        self.control_volume = ControlVolume0DBlock(
            default={
                "dynamic": self.config.dynamic,
                "has_holdup": self.config.has_holdup,
                "property_package": self.config.property_package,
                "property_package_args": self.config.property_package_args
            })

        self.control_volume.add_state_blocks(has_phase_equilibrium=True)

        self.control_volume.add_material_balances(
            balance_type=self.config.material_balance_type,
            has_phase_equilibrium=True)

        self.control_volume.add_energy_balances(
            balance_type=self.config.energy_balance_type,
            has_heat_transfer=True)

        self.control_volume.add_momentum_balances(
            balance_type=self.config.momentum_balance_type,
            has_pressure_change=self.config.has_pressure_change)

        # Get liquid and vapor phase objects from the property package
        # to be used below. Avoids repition.
        _liquid_list = []
        _vapor_list = []
        for p in self.config.property_package.phase_list:
            pobj = self.config.property_package.get_phase(p)
            if pobj.is_vapor_phase():
                _vapor_list.append(p)
            elif pobj.is_liquid_phase():
                _liquid_list.append(p)
            else:
                _liquid_list.append(p)
                model_log.warning(
                    "A non-liquid/non-vapor phase was detected but will "
                    "be treated as a liquid.")

        # Create a pyomo set for indexing purposes. This set is appended to
        # model otherwise results in an abstract set.
        self._liquid_set = Set(initialize=_liquid_list)
        self._vapor_set = Set(initialize=_vapor_list)

        self._make_ports()

        if self.config.condenser_type == CondenserType.totalCondenser:

            self._make_splits_total_condenser()

            if (self.config.temperature_spec == TemperatureSpec.atBubblePoint):
                # Option 1: if true, condition for total condenser
                # (T_cond = T_bubble)
                # Option 2: if this is false, then user has selected
                # custom temperature spec and needs to fix an outlet
                # temperature.
                def rule_total_cond(self, t):
                    return self.control_volume.properties_out[t].\
                        temperature == self.control_volume.properties_out[t].\
                        temperature_bubble

                self.eq_total_cond_spec = Constraint(self.flowsheet().time,
                                                     rule=rule_total_cond)

        else:
            self._make_splits_partial_condenser()

        # Add object reference to variables of the control volume
        # Reference to the heat duty
        self.heat_duty = Reference(self.control_volume.heat[:])

        # Reference to the pressure drop (if set to True)
        if self.config.has_pressure_change:
            self.deltaP = Reference(self.control_volume.deltaP[:])

    def _make_ports(self):

        # Add Ports for the condenser
        # Inlet port (the vapor from the top tray)
        self.add_inlet_port()

        # Outlet ports that always exist irrespective of condenser type
        self.reflux = Port(noruleinit=True,
                           doc="Reflux stream that is"
                           " returned to the top tray.")
        self.distillate = Port(noruleinit=True,
                               doc="Distillate stream that is"
                               " the top product.")

        if self.config.condenser_type == CondenserType.partialCondenser:
            self.vapor_outlet = Port(noruleinit=True,
                                     doc="Vapor outlet port from a "
                                     "partial condenser")
        # Add codnenser specific variables
        self.reflux_ratio = Var(initialize=1,
                                doc="Reflux ratio for the condenser")

    def _make_splits_total_condenser(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:
                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.reflux.add(Reference(var), k)

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

            elif "flow" in k:
                # Create references and populate the extensive variables
                # This is for vars that are not indexed
                if not member_list[k].is_indexed():
                    # Expression for reflux flow and relation to the
                    # reflux_ratio variable

                    def rule_reflux_flow(self, t):
                        return self.control_volume.properties_out[t].\
                            component(member_list[k].local_name) * \
                            (self.reflux_ratio / (1 + self.reflux_ratio))

                    self.e_reflux_flow = Expression(self.flowsheet().time,
                                                    rule=rule_reflux_flow)
                    self.reflux.add(self.e_reflux_flow, k)

                    # Expression for distillate flow and relation to the
                    # reflux_ratio variable
                    def rule_distillate_flow(self, t):
                        return self.control_volume.properties_out[t].\
                            component(member_list[k].local_name) / \
                            (1 + self.reflux_ratio)

                    self.e_distillate_flow = Expression(
                        self.flowsheet().time, rule=rule_distillate_flow)
                    self.distillate.add(self.e_distillate_flow, k)
                else:
                    # Create references and populate the extensive variables
                    # This is for vars that are indexed by phase, comp or both.
                    index_set = member_list[k].index_set()

                    def rule_reflux_flow(self, t, *args):
                        return self.control_volume.properties_out[t].\
                            component(member_list[k].local_name)[args] * \
                            (self.reflux_ratio / (1 + self.reflux_ratio))

                    self.e_reflux_flow = Expression(self.flowsheet().time,
                                                    index_set,
                                                    rule=rule_reflux_flow)
                    self.reflux.add(self.e_reflux_flow, k)

                    def rule_distillate_flow(self, t, *args):
                        return self.control_volume.properties_out[t].\
                            component(member_list[k].local_name)[args] / \
                            (1 + self.reflux_ratio)

                    self.e_distillate_flow = Expression(
                        self.flowsheet().time,
                        index_set,
                        rule=rule_distillate_flow)
                    self.distillate.add(self.e_distillate_flow, k)

            else:
                raise PropertyNotSupportedError(
                    "Unrecognized names for flow variables encountered while "
                    "building the condenser ports.")

    def _make_splits_partial_condenser(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.reflux.add(Reference(var), k)

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

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

            elif "frac" 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:
                    if "mole" in k:  # 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.control_volume.properties_out[0],
                                   "mole_frac_phase_comp") and \
                            hasattr(self.control_volume.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.control_volume.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 k:
                        if hasattr(self.control_volume.properties_out[0],
                                   "mass_frac_phase_comp") and \
                            hasattr(self.control_volume.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.control_volume.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 liquid phase mole fraction
                    def rule_liq_frac(self, t, i):
                        if not flow_phase_comp:
                            sum_flow_comp = sum(
                                self.control_volume.properties_out[t].
                                component(local_name_frac)[p, i] *
                                self.control_volume.properties_out[t].
                                component(local_name_flow)[p]
                                for p in self._liquid_set)

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

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

                    self.e_liq_frac = Expression(self.flowsheet().time,
                                                 index_set,
                                                 rule=rule_liq_frac)

                    # Rule for vapor phase mass/mole fraction
                    def rule_vap_frac(self, t, i):
                        if not flow_phase_comp:
                            sum_flow_comp = sum(
                                self.control_volume.properties_out[t].
                                component(local_name_frac)[p, i] *
                                self.control_volume.properties_out[t].
                                component(local_name_flow)[p]
                                for p in self._vapor_set)
                            return sum_flow_comp / sum(
                                self.control_volume.properties_out[t].
                                component(local_name_flow)[p]
                                for p in self._vapor_set)
                        else:
                            sum_flow_comp = sum(
                                self.control_volume.properties_out[t].
                                component(local_name_flow)[p, i]
                                for p in self._vapor_set)

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

                    self.e_vap_frac = Expression(self.flowsheet().time,
                                                 index_set,
                                                 rule=rule_vap_frac)

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

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

                    # add the reference and variable name to the
                    # vapor port
                    self.vapor_outlet.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 reflux port
                    self.reflux.add(Reference(var), k)

                    # add the reference and variable name to the distillate port
                    self.distillate.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 phase flow
                        def rule_vap_flow(self, t):
                            return sum(self.control_volume.properties_out[t].
                                       component(local_name)[p]
                                       for p in self._vapor_set)

                        self.e_vap_flow = Expression(self.flowsheet().time,
                                                     rule=rule_vap_flow)

                        # Rule to link the liq phase flow to the reflux
                        def rule_reflux_flow(self, t):
                            return sum(self.control_volume.properties_out[t].
                                       component(local_name)[p]
                                       for p in self._liquid_set) * \
                                (self.reflux_ratio / (1 + self.reflux_ratio))

                        self.e_reflux_flow = Expression(self.flowsheet().time,
                                                        rule=rule_reflux_flow)

                        # Rule to link the liq flow to the distillate
                        def rule_distillate_flow(self, t):
                            return sum(self.control_volume.properties_out[t].
                                       component(local_name)[p]
                                       for p in self._liquid_set) / \
                                (1 + self.reflux_ratio)

                        self.e_distillate_flow = Expression(
                            self.flowsheet().time, rule=rule_distillate_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 phase flow to the vapor outlet
                        def rule_vap_flow(self, t, i):
                            return sum(self.control_volume.properties_out[t].
                                       component(local_name)[p, i]
                                       for p in self._vapor_set)

                        self.e_vap_flow = Expression(self.flowsheet().time,
                                                     index_set,
                                                     rule=rule_vap_flow)

                        # Rule to link the liq flow to the reflux
                        def rule_reflux_flow(self, t, i):
                            return sum(self.control_volume.properties_out[t].
                                       component(local_name)[p, i]
                                       for p in self._liquid_set) * \
                                (self.reflux_ratio / (1 + self.reflux_ratio))

                        self.e_reflux_flow = Expression(self.flowsheet().time,
                                                        index_set,
                                                        rule=rule_reflux_flow)

                        # Rule to link the liq flow to the distillate
                        def rule_distillate_flow(self, t, i):
                            return sum(self.control_volume.properties_out[t].
                                       component(local_name)[p, i]
                                       for p in self._liquid_set) / \
                                (1 + self.reflux_ratio)

                        self.e_distillate_flow = Expression(
                            self.flowsheet().time,
                            index_set,
                            rule=rule_distillate_flow)

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

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

                    # add the reference and variable name to the
                    # distillate port
                    self.vapor_outlet.add(self.e_vap_flow, k)
            elif "enth" in k:
                if "phase" not in k:
                    # assumes total mixture enthalpy (enth_mol or enth_mass)
                    # and hence should not be indexed by phase
                    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.")

                    # NOTE:pass phase index when generating expression only
                    # when multiple liquid or vapor phases detected
                    # else ensure consistency with state vars and do not
                    # add phase index to the port members. Hence, the check
                    # for length of local liq and vap phase sets.

                    # 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 sum(
                            self.control_volume.properties_out[t].component(
                                local_name)[p] for p in self._vapor_set)

                    self.e_vap_enth = Expression(self.flowsheet().time,
                                                 rule=rule_vap_enth)

                    # Rule to link the liq enthalpy to the reflux.
                    # Setting the enthalpy to the
                    # enth_mol_phase['Liq'] value from the state block
                    def rule_reflux_enth(self, t):
                        return sum(
                            self.control_volume.properties_out[t].component(
                                local_name)[p] for p in self._liquid_set)

                    self.e_reflux_enth = Expression(self.flowsheet().time,
                                                    rule=rule_reflux_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_distillate_enth(self, t):
                        return sum(
                            self.control_volume.properties_out[t].component(
                                local_name)[p] for p in self._liquid_set)

                    self.e_distillate_enth = Expression(
                        self.flowsheet().time, rule=rule_distillate_enth)

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

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

                    # add the reference and variable name to the
                    # distillate port
                    self.vapor_outlet.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 reflux port
                    self.reflux.add(Reference(var), k)

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

                    # add the reference and variable name to the
                    # vapor outlet port
                    self.vapor_outlet.add(Reference(var), k)
                else:
                    raise PropertyNotSupportedError(
                        "Unrecognized enthalpy state variable encountered "
                        "while building ports for the condenser. Only total "
                        "mixture enthalpy or enthalpy by phase are supported.")

    def initialize(self, solver=None, outlvl=idaeslog.NOTSET):

        # TODO: Fix the inlets to the condenser to the vapor flow from
        # the top tray or take it as an argument to this method.

        init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
        solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")

        if self.config.temperature_spec == TemperatureSpec.customTemperature:
            if degrees_of_freedom(self) != 0:
                raise ConfigurationError(
                    "Degrees of freedom is not 0 during initialization. "
                    "Check if outlet temperature has been fixed in addition "
                    "to the other inputs required as customTemperature was "
                    "selected for temperature_spec config argument.")

        if self.config.condenser_type == CondenserType.totalCondenser:
            self.eq_total_cond_spec.deactivate()

        # Initialize the inlet and outlet state blocks
        self.control_volume.initialize(outlvl=outlvl)

        # Activate the total condenser spec
        if self.config.condenser_type == CondenserType.totalCondenser:
            self.eq_total_cond_spec.activate()

        if solver is not None:
            with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
                res = solver.solve(self, tee=slc.tee)
            init_log.info("Initialization Complete, {}.".format(
                idaeslog.condition(res)))
        else:
            init_log.warning(
                "Solver not provided during initialization, proceeding"
                " with deafult solver in idaes.")
            solver = get_default_solver()
            with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
                res = solver.solve(self, tee=slc.tee)
            init_log.info("Initialization Complete, {}.".format(
                idaeslog.condition(res)))

    def _get_performance_contents(self, time_point=0):
        var_dict = {}
        if hasattr(self, "heat_duty"):
            var_dict["Heat Duty"] = self.heat_duty[time_point]
        if hasattr(self, "deltaP"):
            var_dict["Pressure Change"] = self.deltaP[time_point]

        return {"vars": var_dict}

    def _get_stream_table_contents(self, time_point=0):
        stream_attributes = {}

        if self.config.condenser_type == CondenserType.totalCondenser:
            stream_dict = {
                "Inlet": "inlet",
                "Reflux": "reflux",
                "Distillate": "distillate"
            }
        else:
            stream_dict = {
                "Inlet": "inlet",
                "Vapor Outlet": "vapor_outlet",
                "Reflux": "reflux",
                "Distillate": "distillate"
            }

        for n, v in stream_dict.items():
            port_obj = getattr(self, v)

            stream_attributes[n] = {}

            for k in port_obj.vars:
                for i in port_obj.vars[k].keys():
                    if isinstance(i, float):
                        stream_attributes[n][k] = value(
                            port_obj.vars[k][time_point])
                    else:
                        if len(i) == 2:
                            kname = str(i[1])
                        else:
                            kname = str(i[1:])
                        stream_attributes[n][k + " " + kname] = \
                            value(port_obj.vars[k][time_point, i[1:]])

        return DataFrame.from_dict(stream_attributes, orient="columns")
def test_getattr_does_not_exist(m):
    with pytest.raises(PropertyNotSupportedError):
        m.p.cons = Constraint(expr=m.p.does_not_exist == 1)
Exemple #29
0
class HelmholtzStateBlockData(StateBlockData):
    """
    This is a base clase for Helmholtz equations of state using IDAES standard
    Helmholtz EOS external functions written in C++.
    """

    def initialize(self, *args, **kwargs):
        # With this particualr property pacakage there is not need for
        # initialization
        pass

    def _external_functions(self):
        """Create ExternalFunction components.  This includes some external
        functions that are not usually used for testing purposes."""
        plib = self.config.parameters.plib
        self.func_p = EF(library=plib, function="p")
        self.func_u = EF(library=plib, function="u")
        self.func_s = EF(library=plib, function="s")
        self.func_h = EF(library=plib, function="h")
        self.func_hvpt = EF(library=plib, function="hvpt")
        self.func_hlpt = EF(library=plib, function="hlpt")
        self.func_tau = EF(library=plib, function="tau")
        self.func_vf = EF(library=plib, function="vf")
        self.func_g = EF(library=plib, function="g")
        self.func_f = EF(library=plib, function="f")
        self.func_cv = EF(library=plib, function="cv")
        self.func_cp = EF(library=plib, function="cp")
        self.func_w = EF(library=plib, function="w")
        self.func_delta_liq = EF(library=plib, function="delta_liq")
        self.func_delta_vap = EF(library=plib, function="delta_vap")
        self.func_delta_sat_l = EF(library=plib, function="delta_sat_l")
        self.func_delta_sat_v = EF(library=plib, function="delta_sat_v")
        self.func_p_sat = EF(library=plib, function="p_sat")
        self.func_tau_sat = EF(library=plib, function="tau_sat")
        self.func_phi0 = EF(library=plib, function="phi0")
        self.func_phi0_delta = EF(library=plib, function="phi0_delta")
        self.func_phi0_delta2 = EF(library=plib, function="phi0_delta2")
        self.func_phi0_tau = EF(library=plib, function="phi0_tau")
        self.func_phi0_tau2 = EF(library=plib, function="phi0_tau2")
        self.func_phir = EF(library=plib, function="phir")
        self.func_phir_delta = EF(library=plib, function="phir_delta")
        self.func_phir_delta2 = EF(library=plib, function="phir_delta2")
        self.func_phir_tau = EF(library=plib, function="phir_tau")
        self.func_phir_tau2 = EF(library=plib, function="phir_tau2")
        self.func_phir_delta_tau = EF(library=plib, function="phir_delta_tau")

    def _state_vars(self):
        """ Create the state variables
        """
        self.flow_mol = Var(
            initialize=1, domain=NonNegativeReals, doc="Total flow [mol/s]"
        )
        self.scaling_factor[self.flow_mol] = 1e-3

        if self.state_vars == StateVars.PH:
            self.pressure = Var(
                domain=PositiveReals,
                initialize=1e5,
                doc="Pressure [Pa]",
                bounds=(1, 1e9),
            )
            self.enth_mol = Var(
                initialize=1000, doc="Total molar enthalpy (J/mol)", bounds=(1, 1e5)
            )
            self.scaling_factor[self.enth_mol] = 1e-3

            P = self.pressure / 1000.0  # Pressure expr [kPA] (for external func)
            h_mass = self.enth_mol / self.mw / 1000  # enthalpy expr [kJ/kg]
            phase_set = self.config.parameters.config.phase_presentation

            self.temperature = Expression(
                expr=self.temperature_crit / self.func_tau(h_mass, P),
                doc="Temperature (K)",
            )
            if phase_set == PhaseType.MIX or phase_set == PhaseType.LG:
                self.vapor_frac = Expression(
                    expr=self.func_vf(h_mass, P),
                    doc="Vapor mole fraction (mol vapor/mol total)",
                )
            elif phase_set == PhaseType.L:
                self.vapor_frac = Expression(
                    expr=0.0, doc="Vapor mole fraction (mol vapor/mol total)"
                )
            elif phase_set == PhaseType.G:
                self.vapor_frac = Expression(
                    expr=1.0, doc="Vapor mole fraction (mol vapor/mol total)"
                )

            # For variables that show up in ports specify extensive/intensive
            self.extensive_set = ComponentSet((self.flow_mol,))
            self.intensive_set = ComponentSet((self.enth_mol, self.pressure))

        elif self.state_vars == StateVars.TPX:
            self.temperature = Var(
                initialize=300, doc="Temperature [K]", bounds=(200, 3e3)
            )

            self.pressure = Var(
                domain=PositiveReals,
                initialize=1e5,
                doc="Pressure [Pa]",
                bounds=(1, 1e9),
            )

            self.vapor_frac = Var(initialize=0.0, doc="Vapor fraction [none]")

            # enth_mol is defined later, since in this case it needs
            # enth_mol_phase to be defined first

            # For variables that show up in ports specify extensive/intensive
            self.extensive_set = ComponentSet((self.flow_mol,))
            self.intensive_set = ComponentSet(
                (self.temperature, self.pressure, self.vapor_frac)
            )
        self.scaling_factor[self.temperature] = 1e-1
        self.scaling_factor[self.pressure] = 1e-6
        self.scaling_factor[self.vapor_frac] = 1e1

    def _tpx_phase_eq(self):
        # Saturation pressure
        eps_pu = self.config.parameters.smoothing_pressure_under
        eps_po = self.config.parameters.smoothing_pressure_over
        priv_plist = self.config.parameters.private_phase_list
        plist = self.config.parameters.phase_list
        rhoc = self.config.parameters.dens_mass_crit

        P = self.pressure / 1000  # expression for pressure in kPa
        Psat = self.pressure_sat / 1000.0  # expression for Psat in kPA
        vf = self.vapor_frac
        tau = self.tau

        # Terms for determining if you are above, below, or at the Psat
        self.P_under_sat = Expression(
            expr=smooth_max(0, Psat - P, eps_pu),
            doc="pressure above Psat, 0 if liqid exists [kPa]",
        )
        self.P_over_sat = Expression(
            expr=smooth_max(0, P - Psat, eps_po),
            doc="pressure below Psat, 0 if vapor exists [kPa]",
        )

        # Calculate liquid and vapor density.  If the phase doesn't exist,
        # density will be calculated at the saturation or critical pressure
        def rule_dens_mass(b, p):
            if p == "Liq":
                self.scaling_factor[self.dens_mass_phase[p]] = 1e-2
                return rhoc * self.func_delta_liq(P + self.P_under_sat, tau)
            else:
                self.scaling_factor[self.dens_mass_phase[p]] = 1e1
                return rhoc * self.func_delta_vap(P - self.P_over_sat, tau)

        self.dens_mass_phase = Expression(priv_plist, rule=rule_dens_mass)

        # Reduced Density (no _mass_ identifier because mass or mol is same)
        def rule_dens_red(b, p):
            return self.dens_mass_phase[p] / rhoc

        self.dens_phase_red = Expression(
            priv_plist, rule=rule_dens_red, doc="reduced density [unitless]"
        )

        # If there is only one phase fix the vapor fraction appropriately
        if len(plist) == 1:
            if "Vap" in plist:
                self.vapor_frac.fix(1.0)
            else:
                self.vapor_frac.fix(0.0)
        elif not self.config.defined_state:
            self.eq_complementarity = Constraint(
                expr=0 == (vf * self.P_over_sat - (1 - vf) * self.P_under_sat)
            )
            self.scaling_expression[self.eq_complementarity] = 10 / self.pressure

        # eq_sat can activated to force the pressure to be the saturation
        # pressure, if you use this constraint deactivate eq_complementarity
        self.eq_sat = Constraint(expr=P / 1000.0 == Psat / 1000.0)
        self.scaling_expression[self.eq_sat] = 1000 / self.pressure
        self.eq_sat.deactivate()


    def build(self, *args):
        """
        Callable method for Block construction
        """
        super().build(*args)

        # Create the scaling suffixes for the state block
        self.scaling_factor = Suffix(direction=Suffix.EXPORT)
        self.scaling_expression = Suffix()

        # Check if the library is available.
        self.available = self.config.parameters.available
        if not self.available:
            _log.error("Library file '{}' not found. Was it installed?".format(
                    self.config.parameter.plib
                )
            )

        # Add external functions
        self._external_functions()

        # Which state vars to use
        self.state_vars = self.config.parameters.state_vars
        # The private phase list contains phases that may be present and is
        # used internally.  If using the single mixed phase option the phase
        # list would be mixed while the private phase list would be ["Liq, "Vap"]
        phlist = self.config.parameters.private_phase_list
        pub_phlist = self.config.parameters.phase_list
        component_list = self.config.parameters.component_list
        phase_set = self.config.parameters.config.phase_presentation
        self.phase_equilibrium_list = self.config.parameters.phase_equilibrium_list

        # Expressions that link to some parameters in the param block, which
        # are commonly needed, this lets you get the parameters with scale
        # factors directly from the state block
        self.temperature_crit = Expression(expr=self.config.parameters.temperature_crit)
        self.scaling_factor[self.temperature_crit] = 1e-2
        self.pressure_crit = Expression(expr=self.config.parameters.pressure_crit)
        self.scaling_factor[self.pressure_crit] = 1e-6
        self.dens_mass_crit = Expression(expr=self.config.parameters.dens_mass_crit)
        self.scaling_factor[self.dens_mass_crit] = 1e-2
        self.gas_const = Expression(expr=self.config.parameters.gas_const)
        self.scaling_factor[self.gas_const] = 1e0
        self.mw = Expression(
            expr=self.config.parameters.mw, doc="molecular weight [kg/mol]"
        )
        self.scaling_factor[self.mw] = 1e3

        # create the appropriate state variables
        self._state_vars()

        # Some parameters/variables show up in several expressions, so to enhance
        # readability and compactness, give them short aliases
        Tc = self.config.parameters.temperature_crit
        rhoc = self.config.parameters.dens_mass_crit
        mw = self.mw
        P = self.pressure / 1000.0  # Pressure expr [kPA] (for external func)
        T = self.temperature
        vf = self.vapor_frac

        # Saturation temperature expression
        self.temperature_sat = Expression(
            expr=Tc / self.func_tau_sat(P), doc="Stauration temperature (K)"
        )
        self.scaling_factor[self.temperature_sat] = 1e-2

        # Saturation tau (tau = Tc/T)
        self.tau_sat = Expression(expr=self.func_tau_sat(P))

        # Reduced temperature
        self.temperature_red = Expression(
            expr=T / Tc, doc="reduced temperature T/Tc (unitless)"
        )
        self.scaling_factor[self.temperature_red] = 1

        self.tau = Expression(expr=Tc / T, doc="Tc/T (unitless)")
        tau = self.tau

        # Saturation pressure
        self.pressure_sat = Expression(
            expr=1000 * self.func_p_sat(tau), doc="Saturation pressure (Pa)"
        )
        self.scaling_factor[self.pressure_sat] = 1e-5

        if self.state_vars == StateVars.PH:
            # If TPx state vars the expressions are given in _tpx_phase_eq
            # Calculate liquid and vapor density.  If the phase doesn't exist,
            # density will be calculated at the saturation or critical pressure
            # depending on whether the temperature is above the critical
            # temperature supercritical fluid is considered to be the liquid
            # phase
            def rule_dens_mass(b, p):
                if p == "Liq":
                    self.scaling_factor[self.dens_mass_phase[p]] = 1e-2
                    return rhoc * self.func_delta_liq(P, tau)
                else:
                    self.scaling_factor[self.dens_mass_phase[p]] = 1e1
                    return rhoc * self.func_delta_vap(P, tau)

            self.dens_mass_phase = Expression(
                phlist, rule=rule_dens_mass, doc="Mass density by phase (kg/m3)"
            )

            # Reduced Density (no _mass_ identifier as mass or mol is same)
            def rule_dens_red(b, p):
                self.scaling_factor[self.dens_phase_red[p]] = 1
                return self.dens_mass_phase[p] / rhoc

            self.dens_phase_red = Expression(
                phlist, rule=rule_dens_red, doc="reduced density (unitless)"
            )

        elif self.state_vars == StateVars.TPX:
            self._tpx_phase_eq()
        delta = self.dens_phase_red

        # Phase property expressions all converted to SI

        # Saturated Enthalpy
        def rule_enth_mol_sat_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.enth_mol_sat_phase[p]] = 1e-2
                return 1000 * mw * self.func_hlpt(P, self.tau_sat)
            elif p == "Vap":
                self.scaling_factor[self.enth_mol_sat_phase[p]] = 1e-4
                return 1000 * mw * self.func_hvpt(P, self.tau_sat)

        self.enth_mol_sat_phase = Expression(
            phlist,
            rule=rule_enth_mol_sat_phase,
            doc="Saturated enthalpy of the phases at pressure (J/mol)",
        )

        self.dh_vap_mol = Expression(
            expr=self.enth_mol_sat_phase["Vap"] - self.enth_mol_sat_phase["Liq"],
            doc="Enthaply of vaporization at pressure and saturation (J/mol)",
        )
        self.scaling_factor[self.dh_vap_mol] = 1e-4

        # Phase Internal Energy
        def rule_energy_internal_mol_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.energy_internal_mol_phase[p]] = 1e-2
            else:
                self.scaling_factor[self.energy_internal_mol_phase[p]] = 1e-4
            return 1000 * mw * self.func_u(delta[p], tau)

        self.energy_internal_mol_phase = Expression(
            phlist,
            rule=rule_energy_internal_mol_phase,
            doc="Phase internal energy or saturated if phase doesn't exist [J/mol]",
        )

        # Phase Enthalpy
        def rule_enth_mol_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.enth_mol_phase[p]] = 1e-2
            elif p == "Vap":
                self.scaling_factor[self.enth_mol_phase[p]] = 1e-4
            return 1000 * mw * self.func_h(delta[p], tau)

        self.enth_mol_phase = Expression(
            phlist,
            rule=rule_enth_mol_phase,
            doc="Phase enthalpy or saturated if phase doesn't exist [J/mol]",
        )

        # Phase Entropy
        def rule_entr_mol_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.entr_mol_phase[p]] = 1e-1
            elif p == "Vap":
                self.scaling_factor[self.entr_mol_phase[p]] = 1e-1
            return 1000 * mw * self.func_s(delta[p], tau)

        self.entr_mol_phase = Expression(
            phlist,
            rule=rule_entr_mol_phase,
            doc="Phase entropy or saturated if phase doesn't exist [J/mol/K]",
        )

        # Phase constant pressure heat capacity, cp
        def rule_cp_mol_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.cp_mol_phase[p]] = 1e-2
            elif p == "Vap":
                self.scaling_factor[self.cp_mol_phase[p]] = 1e-2
            return 1000 * mw * self.func_cp(delta[p], tau)

        self.cp_mol_phase = Expression(
            phlist,
            rule=rule_cp_mol_phase,
            doc="Phase cp or saturated if phase doesn't exist [J/mol/K]",
        )

        # Phase constant pressure heat capacity, cv
        def rule_cv_mol_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.cv_mol_phase[p]] = 1e-2
            elif p == "Vap":
                self.scaling_factor[self.cv_mol_phase[p]] = 1e-2
            return 1000 * mw * self.func_cv(delta[p], tau)

        self.cv_mol_phase = Expression(
            phlist,
            rule=rule_cv_mol_phase,
            doc="Phase cv or saturated if phase doesn't exist [J/mol/K]",
        )

        # Phase speed of sound
        def rule_speed_sound_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.speed_sound_phase[p]] = 1e-2
            elif p == "Vap":
                self.scaling_factor[self.speed_sound_phase[p]] = 1e-2
            return self.func_w(delta[p], tau)

        self.speed_sound_phase = Expression(
            phlist,
            rule=rule_speed_sound_phase,
            doc="Phase speed of sound or saturated if phase doesn't exist [m/s]",
        )

        # Phase Mole density
        def rule_dens_mol_phase(b, p):
            if p == "Liq":
                self.scaling_factor[self.dens_mol_phase[p]] = 1e-2
            elif p == "Vap":
                self.scaling_factor[self.dens_mol_phase[p]] = 1e-4
            return self.dens_mass_phase[p] / mw

        self.dens_mol_phase = Expression(
            phlist,
            rule=rule_dens_mol_phase,
            doc="Phase mole density or saturated if phase doesn't exist [mol/m3]",
        )

        # Phase fraction
        def rule_phase_frac(b, p):
            self.scaling_factor[self.phase_frac[p]] = 10
            if p == "Vap":
                return vf
            elif p == "Liq":
                return 1.0 - vf

        self.phase_frac = Expression(
            phlist, rule=rule_phase_frac, doc="Phase fraction [unitless]"
        )

        # Component flow (for units that need it)
        def component_flow(b, i):
            self.scaling_factor[self.flow_mol_comp[i]] = 1e-3
            return self.flow_mol

        self.flow_mol_comp = Expression(
            component_list,
            rule=component_flow,
            doc="Total flow (both phases) of component [mol/s]",
        )

        # Total (mixed phase) properties

        # Enthalpy
        if self.state_vars == StateVars.TPX:
            self.enth_mol = Expression(
                expr=sum(self.phase_frac[p] * self.enth_mol_phase[p] for p in phlist)
            )
            self.scaling_factor[self.enth_mol] = 1e-3
        # Internal Energy
        self.energy_internal_mol = Expression(
            expr=sum(
                self.phase_frac[p] * self.energy_internal_mol_phase[p] for p in phlist
            )
        )
        self.scaling_factor[self.energy_internal_mol] = 1e-3
        # Entropy
        self.entr_mol = Expression(
            expr=sum(self.phase_frac[p] * self.entr_mol_phase[p] for p in phlist)
        )
        self.scaling_factor[self.entr_mol] = 1e-1
        # cp
        self.cp_mol = Expression(
            expr=sum(self.phase_frac[p] * self.cp_mol_phase[p] for p in phlist)
        )
        self.scaling_factor[self.cp_mol] = 1e-2
        # cv
        self.cv_mol = Expression(
            expr=sum(self.phase_frac[p] * self.cv_mol_phase[p] for p in phlist)
        )
        self.scaling_factor[self.cv_mol] = 1e-2
        # mass density
        self.dens_mass = Expression(
            expr=1.0
            / sum(self.phase_frac[p] * 1.0 / self.dens_mass_phase[p] for p in phlist)
        )
        self.scaling_factor[self.dens_mass] = 1e0
        # mole density
        self.dens_mol = Expression(
            expr=1.0
            / sum(self.phase_frac[p] * 1.0 / self.dens_mol_phase[p] for p in phlist)
        )
        self.scaling_factor[self.dens_mol] = 1e-3
        # heat capacity ratio
        self.heat_capacity_ratio = Expression(expr=self.cp_mol / self.cv_mol)
        self.scaling_factor[self.heat_capacity_ratio] = 1e1
        # Flows
        self.flow_vol = Expression(
            expr=self.flow_mol / self.dens_mol,
            doc="Total liquid + vapor volumetric flow (m3/s)",
        )
        self.scaling_factor[self.flow_vol] = 100

        self.flow_mass = Expression(
            expr=self.mw * self.flow_mol, doc="mass flow rate [kg/s]"
        )
        self.scaling_factor[self.flow_mass] = 1

        self.enth_mass = Expression(expr=self.enth_mol / mw, doc="Mass enthalpy (J/kg)")
        self.scaling_factor[self.enth_mass] = 1

        # Set the state vars dictionary
        if self.state_vars == StateVars.PH:
            self._state_vars_dict = {
                "flow_mol": self.flow_mol,
                "enth_mol": self.enth_mol,
                "pressure": self.pressure,
            }
        elif self.state_vars == StateVars.TPX and phase_set in (
            PhaseType.MIX,
            PhaseType.LG,
        ):
            self._state_vars_dict = {
                "flow_mol": self.flow_mol,
                "temperature": self.temperature,
                "pressure": self.pressure,
                "vapor_frac": self.vapor_frac,
            }
        elif self.state_vars == StateVars.TPX and phase_set in (
            PhaseType.G,
            PhaseType.L,
        ):
            self._state_vars_dict = {
                "flow_mol": self.flow_mol,
                "temperature": self.temperature,
                "pressure": self.pressure,
            }

        # Define some expressions for the balance terms returned by functions
        # This is just to allow assigning scale factors to the expressions
        # returned
        #
        # Marterial flow term exprsssions
        def rule_material_flow_terms(b, p):
            self.scaling_expression[b.material_flow_terms[p]] = 1 / self.flow_mol
            if p == "Mix":
                return self.flow_mol
            else:
                return self.flow_mol * self.phase_frac[p]

        self.material_flow_terms = Expression(pub_phlist, rule=rule_material_flow_terms)

        # Enthaply flow term expressions
        def rule_enthalpy_flow_terms(b, p):
            if p == "Mix":
                self.scaling_expression[b.enthalpy_flow_terms[p]] = 1 / (
                    self.enth_mol * self.flow_mol
                )
                return self.enth_mol * self.flow_mol
            else:
                self.scaling_expression[b.enthalpy_flow_terms[p]] = 1 / (
                    self.enth_mol_phase[p] * self.phase_frac[p] * self.flow_mol
                )
                return self.enth_mol_phase[p] * self.phase_frac[p] * self.flow_mol

        self.enthalpy_flow_terms = Expression(pub_phlist, rule=rule_enthalpy_flow_terms)

        # Energy density term expressions
        def rule_energy_density_terms(b, p):
            if p == "Mix":
                self.scaling_expression[b.energy_density_terms[p]] = 1 / (
                    self.energy_internal_mol * self.flow_mol
                )
                return self.dens_mol * self.energy_internal_mol
            else:
                self.scaling_expression[b.energy_density_terms[p]] = 1 / (
                    self.dens_mol_phase[p] * self.energy_internal_mol_phase[p]
                )
                return self.dens_mol_phase[p] * self.energy_internal_mol_phase[p]

        self.energy_density_terms = Expression(
            pub_phlist, rule=rule_energy_density_terms
        )

    def get_material_flow_terms(self, p, j):
        return self.material_flow_terms[p]

    def get_enthalpy_flow_terms(self, p):
        return self.enthalpy_flow_terms[p]

    def get_material_density_terms(self, p, j):
        if p == "Mix":
            return self.dens_mol
        else:
            return self.dens_mol_phase[p]

    def get_energy_density_terms(self, p):
        return self.energy_density_terms[p]

    def default_material_balance_type(self):
        return MaterialBalanceType.componentTotal

    def default_energy_balance_type(self):
        return EnergyBalanceType.enthalpyTotal

    def define_state_vars(self):
        return self._state_vars_dict

    def define_display_vars(self):
        return {
            "Molar Flow (mol/s)": self.flow_mol,
            "Mass Flow (kg/s)": self.flow_mass,
            "T (K)": self.temperature,
            "P (Pa)": self.pressure,
            "Vapor Fraction": self.vapor_frac,
            "Molar Enthalpy (J/mol)": self.enth_mol_phase,
        }

    def extensive_state_vars(self):
        return self.extensive_set

    def intensive_state_vars(self):
        return self.intensive_set

    def model_check(self):
        pass
def test_getattr_not_callable(m):
    with pytest.raises(PropertyPackageError):
        m.p.cons = Constraint(expr=m.p.not_callable == 1)
def test_getattr_not_supported(m):
    with pytest.raises(PropertyNotSupportedError):
        m.p.cons = Constraint(expr=m.p.not_supported == 1)
Exemple #32
0
    def test_varlist_aggregator(self):
        m = ConcreteModel()
        m.flow = VarList()
        m.phase = Var(bounds=(1, 3))
        m.CON = Connector()
        m.CON.add(m.flow, aggregate=lambda m, v: sum(v[i] for i in v) == 0)
        m.CON.add(m.phase)
        m.ECON2 = Connector()
        m.ECON1 = Connector()

        # 2 constraints: one has a connector, the other doesn't.  The
        # former should be deactivated and expanded, the latter should
        # be left untouched.
        m.c = Constraint(expr=m.CON == m.ECON1)
        m.d = Constraint(expr=m.ECON2 == m.CON)

        self.assertEqual(len(list(m.component_objects(Constraint))), 2)
        self.assertEqual(len(list(m.component_data_objects(Constraint))), 2)

        TransformationFactory('core.expand_connectors').apply_to(m)
        #m.pprint()

        self.assertEqual(len(list(m.component_objects(Constraint))), 5)
        self.assertEqual(len(list(m.component_data_objects(Constraint))), 7)
        self.assertFalse(m.c.active)
        self.assertTrue(m.component('c.expanded').active)
        self.assertFalse(m.d.active)
        self.assertTrue(m.component('d.expanded').active)

        self.assertEqual(len(m.flow), 2)

        os = StringIO()
        m.component('c.expanded').pprint(ostream=os)
        self.assertEqual(
            os.getvalue(),
            """c.expanded : Size=2, Index=c.expanded_index, Active=True
    Key : Lower : Body                      : Upper : Active
      1 :   0.0 : flow[1] - ECON1.auto.flow :   0.0 :   True
      2 :   0.0 :  phase - ECON1.auto.phase :   0.0 :   True
""")

        os = StringIO()
        m.component('d.expanded').pprint(ostream=os)
        self.assertEqual(
            os.getvalue(),
            """d.expanded : Size=2, Index=d.expanded_index, Active=True
    Key : Lower : Body                      : Upper : Active
      1 :   0.0 : ECON2.auto.flow - flow[2] :   0.0 :   True
      2 :   0.0 :  ECON2.auto.phase - phase :   0.0 :   True
""")

        os = StringIO()
        m.component('CON.flow.aggregate').pprint(ostream=os)
        self.assertEqual(
            os.getvalue(),
            """CON.flow.aggregate : Size=1, Index=None, Active=True
    Key  : Lower : Body              : Upper : Active
    None :   0.0 : flow[1] + flow[2] :   0.0 :   True
""")

        os = StringIO()
        m.CON.pprint(ostream=os)
        self.assertEqual(
            os.getvalue(), """CON : Size=1, Index=None
    Key  : Name  : Size : Variable
    None :  flow :    * :     flow
         : phase :    1 :    phase
""")