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)
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)
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
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[:])
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)
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)
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]
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)
# 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
# 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]
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)
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)
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)
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)
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)
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)
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)
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)
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 """)