def _indexed_Block_rule(b, i): b.x1 = Var() b.x1._domain = None b.x2 = Var() b.x2._domain = None b.x3 = Var() b.x3._domain = None b.x4 = Var() b.x4._domain = None b.x5 = Var() b.x5._domain = None return b
def build_Var(): """Build a Var and delete any references to external objects so its size can be computed.""" obj = Var() obj.construct() obj._domain = None return obj
def set_as_constraint(self, uncertain_params, **kwargs): """ Function to generate constraints for the FactorModelSet uncertainty set. Args: uncertain_params: uncertain parameter objects for writing constraint objects """ model = kwargs['model'] # === Ensure dimensions if len(uncertain_params) != len(self.origin): raise AttributeError("Dimensions of origin and uncertain_param lists must be equal.") # Make F-dim cassi variable n = list(range(self.number_of_factors)) model.util.cassi = Var(n, initialize=0, bounds=(-1, 1)) conlist = ConstraintList() conlist.construct() disturbances = [sum(self.psi_mat[i][j] * model.util.cassi[j] for j in n) for i in range(len(uncertain_params))] # Make n equality constraints for i in range(len(uncertain_params)): conlist.add(self.origin[i] + disturbances[i] == uncertain_params[i]) conlist.add(sum(model.util.cassi[i] for i in n) <= +self.beta * self.number_of_factors) conlist.add(sum(model.util.cassi[i] for i in n) >= -self.beta * self.number_of_factors) return conlist
def point_in_set(self, point): """ Calculates if supplied ``point`` is contained in the uncertainty set. Returns True or False. Args: point: The point being checked for membership in the set. The coordinates of the point should be supplied in the same order as the elements of ``uncertain_params`` that is to be supplied to the PyROS solve statement. This point must match the dimension of the uncertain parameters of the set. """ # === Ensure point is of correct dimensionality as the uncertain parameters if len(point) != self.dim: raise AttributeError("Point must have same dimensions as uncertain parameters.") m = ConcreteModel() the_params = [] for i in range(self.dim): m.add_component("x_%s" % i, Var(initialize=point[i])) the_params.append(getattr(m, "x_%s" % i)) # === Generate constraint for set set_constraint = self.set_as_constraint(uncertain_params=the_params) # === value() returns True if the constraint is satisfied, False else. is_in_set = all(value(con.expr) for con in set_constraint.values()) return is_in_set
def set_as_constraint(self, uncertain_params, **kwargs): """ Function to generate constraints for the CardinalitySet uncertainty set. Args: uncertain_params: uncertain parameter objects for writing constraint objects """ # === Ensure dimensions if len(uncertain_params) != len(self.origin): raise AttributeError("Dimensions of origin and uncertain_param lists must be equal.") model = kwargs['model'] set_i = list(range(len(uncertain_params))) model.util.cassi = Var(set_i, initialize=0, bounds=(0, 1)) # Make n equality constraints conlist = ConstraintList() conlist.construct() for i in set_i: conlist.add(self.origin[i] + self.positive_deviation[i] * model.util.cassi[i] == uncertain_params[i]) conlist.add(sum(model.util.cassi[i] for i in set_i) <= self.gamma) return conlist
def is_empty_intersection(self, uncertain_params, nlp_solver): """ Determine if intersection is empty Args: uncertain_params: list of uncertain parameters nlp_solver: a Pyomo Solver object for solving NLPs """ # === Non-emptiness check for the set intersection is_empty_intersection = True if any(a_set.type == "discrete" for a_set in self.all_sets): disc_sets = (a_set for a_set in self.all_sets if a_set.type == "discrete") disc_set = min(disc_sets, key=lambda x: len(x.scenarios)) # minimum set of scenarios # === Ensure there is at least one scenario from this discrete set which is a member of all other sets for scenario in disc_set.scenarios: if all(a_set.point_in_set(point=scenario) for a_set in self.all_sets): is_empty_intersection = False break else: # === Compile constraints and solve NLP m = ConcreteModel() m.obj = Objective(expr=0) # dummy objective required if using baron m.param_vars = Var(uncertain_params.index_set()) for a_set in self.all_sets: m.add_component(a_set.type + "_constraints", a_set.set_as_constraint(uncertain_params=m.param_vars)) try: res = nlp_solver.solve(m) except: raise ValueError("Solver terminated with an error while checking set intersection non-emptiness.") if check_optimal_termination(res): is_empty_intersection = False return is_empty_intersection
def var(self, *args, **kwds): """ Declare a variable. Parameters ---------- \*args The first argument is a string for the variable name used by Pyomo. The remaining arguments are assumed to be index sets for the variable. \**kwargs The keyword arguments are the same as the keyword arguments supported by the Pyomo Var component. Returns ------- Variable object If the variable is not indexed, then the return type is a single Pyomo variable object. If the variable is indexed, then the return type is a dictionary of Pyomo variable objects. """ _args = args[1:] name = args[0] # If the variable name is "x", then the following is equivalent to: # self.model.x = Var(*_args, **kwds) _v = Var(*_args, **kwds) setattr(self.model, name, _v) return _v
def build_BlockData_with_objects(): """Build an empty _BlockData""" obj = _BlockData(build_BlockData_with_objects.owner) obj.x = Var() obj.x._domain = None obj.c = Constraint() obj.o = Objective() obj._component = None return obj
def build_Block_with_objects(): """Build an empty Block""" obj = Block(concrete=True) obj.construct() obj.x = Var() obj.x._domain = None obj.c = Constraint() obj.o = Objective() return obj
def build_indexed_Var(): """Build an indexed Var with no references to external objects so its size can be computed.""" model = build_indexed_Var.model model.indexed_Var = Var(model.ndx, domain=Integers, bounds=build_indexed_Var.bounds_rule, initialize=build_indexed_Var.initialize_rule) model.indexed_Var._domain = None model.indexed_Var._component = None return model.indexed_Var
def get_hessian_of_constraint(constraint, wrt1=None, wrt2=None, nlp=None): constraints = [constraint] if wrt1 is None and wrt2 is None: variables = list( identify_variables(constraint.expr, include_fixed=False)) wrt1 = variables wrt2 = variables elif wrt1 is not None and wrt2 is not None: variables = wrt1 + wrt2 elif wrt1 is not None: # but wrt2 is None wrt2 = wrt1 variables = wrt1 else: # wrt2 is not None and wrt1 is None wrt1 = wrt2 variables = wrt1 if nlp is None: block = create_subsystem_block(constraints, variables=variables) # Could fix input_vars so I don't evaluate the Hessian with respect # to variables I don't care about... # HUGE HACK: Variables not included in a constraint are not written # to the nl file, so we cannot take the derivative with respect to # them, even though we know this derivative is zero. To work around, # we make sure all variables appear on the block in the form of a # dummy constraint. Then we can take derivatives of any constraint # with respect to them. Conveniently, the extract_submatrix_ # call deals with extracting the variables and constraint we care # about, in the proper order. block._dummy_var = Var() block._dummy_con = Constraint(expr=sum(variables) == block._dummy_var) block._obj = Objective(expr=0.0) nlp = PyomoNLP(block) saved_duals = nlp.get_duals() saved_obj_factor = nlp.get_obj_factor() temp_duals = np.zeros(len(saved_duals)) # NOTE: This makes some assumption about how the Lagrangian is constructed. # TODO: Define the convention we assume and convert if necessary. idx = nlp.get_constraint_indices(constraints)[0] temp_duals[idx] = 1.0 nlp.set_duals(temp_duals) nlp.set_obj_factor(0.0) # NOTE: The returned matrix preserves explicit zeros. I.e. it contains # coordinates for every entry that could possibly be nonzero. submatrix = nlp.extract_submatrix_hessian_lag(wrt1, wrt2) nlp.set_obj_factor(saved_obj_factor) nlp.set_duals(saved_duals) return submatrix
def __call__(self): deprecation_warning( "Relying on core.logical_to_linear to transform " "BooleanVars that do not appear in LogicalConstraints " "is deprecated. Please associate your own binaries if " "you have BooleanVars not used in logical expressions.", version='6.2') parent_block = self._boolvar().parent_block() new_var = Var(domain=Binary) parent_block.add_component( unique_component_name(parent_block, self._boolvar().local_name + "_asbinary"), new_var) self._boolvar()._associated_binary = None self._boolvar().associate_binary_var(new_var) return new_var
def _Split(port, name, index_set, include_splitfrac=False, write_var_sum=True): port_parent = port.parent_block() var = port.vars[name] out_vars = [] no_splitfrac = False dests = port.dests(active=True) if not len(dests): return out_vars if len(dests) == 1: # No need for splitting on one outlet. # Make sure they do not try to fix splitfrac not at 1. splitfracspec = port.get_split_fraction(dests[0]) if splitfracspec is not None: if splitfracspec[0] != 1 and splitfracspec[1] == True: raise ValueError( "Cannot fix splitfrac not at 1 for port '%s' with a " "single dest '%s'" % (port.name, dests[0].name)) no_splitfrac = True if len(dests[0].destination.sources(active=True)) == 1: # This is a 1-to-1 connection, no need for evar, just equality. arc = dests[0] Port._add_equality_constraint(arc, name, index_set) return out_vars for arc in dests: eblock = arc.expanded_block # Make and record new variables for every arc with this member. evar = Port._create_evar(port.vars[name], name, eblock, index_set) out_vars.append(evar) if no_splitfrac: continue # Create and potentially initialize split fraction variables. # This function will be called for every Extensive member of this # port, but we only need one splitfrac variable per arc, so check # if it already exists before making a new one. However, we do not # need a splitfrac if there is only one Extensive data object, # so first check whether or not we need it. if eblock.component("splitfrac") is None: if not include_splitfrac: num_data_objs = 0 for k, v in iteritems(port.vars): if port.is_extensive(k): if v.is_indexed(): num_data_objs += len(v) else: num_data_objs += 1 if num_data_objs > 1: break if num_data_objs <= 1: # Do not make splitfrac, do not make split constraints. # Make sure they didn't specify splitfracs. # This inner loop will only run once. for arc in dests: if port.get_split_fraction(arc) is not None: raise ValueError( "Cannot specify splitfracs for port '%s' " "(found arc '%s') because this port only " "has one variable. To have control over " "splitfracs, please pass the " " include_splitfrac=True argument." % (port.name, arc.name)) no_splitfrac = True continue eblock.splitfrac = Var() splitfracspec = port.get_split_fraction(arc) if splitfracspec is not None: eblock.splitfrac = splitfracspec[0] if splitfracspec[1]: eblock.splitfrac.fix() # Create constraint for this member using splitfrac. cname = "%s_split" % name def rule(m, *args): if len(args): return evar[args] == eblock.splitfrac * var[args] else: return evar == eblock.splitfrac * var con = Constraint(index_set, rule=rule) eblock.add_component(cname, con) if write_var_sum: # Create var total sum constraint: var == sum of evars # Need to alphanum port name in case it is indexed. cname = unique_component_name(port_parent, "%s_%s_outsum" % (alphanum_label_from_name(port.local_name), name)) def rule(m, *args): if len(args): return sum(evar[args] for evar in out_vars) == var[args] else: return sum(evar for evar in out_vars) == var con = Constraint(index_set, rule=rule) port_parent.add_component(cname, con) else: # OR create constraint on splitfrac vars: sum == 1 if no_splitfrac: raise ValueError( "Cannot choose to write split fraction sum constraint for " "ports with a single destination or a single Extensive " "variable.\nSplit fractions are skipped in this case to " "simplify the model.\nPlease use write_var_sum=True on " "this port (the default).") cname = unique_component_name(port_parent, "%s_frac_sum" % alphanum_label_from_name(port.local_name)) con = Constraint(expr= sum(a.expanded_block.splitfrac for a in dests) == 1) port_parent.add_component(cname, con) return out_vars
def declare_variables(model): model.area_beta = Var(model.HP, model.CP, model.ST, initialize=0, domain=NonNegativeReals) model.area_cu_beta = Var(model.HP, initialize=0, domain=NonNegativeReals) model.area_hu_beta = Var(model.CP, initialize=0, domain=NonNegativeReals) # Flow rates model.fh = Var(model.HP, model.CP, model.ST, bounds=fh_bounds,\ doc="Flow rate entering heat exchanger ijk cold side" ) model.fc = Var(model.HP, model.CP, model.ST, bounds=fc_bounds,\ doc="Flow rate entering heat exchanger ijk hot side" ) # Per exchanger outlet model.thx = Var(model.HP, model.CP, model.ST, bounds=thx_bounds,\ doc="Outlet temperature of the heat exchanger ijk hot side" ) model.tcx = Var(model.HP, model.CP, model.ST, bounds=tcx_bounds,\ doc="Outlet temperature of the heat exchanger ijk cold side" ) # Temperature approaches model.dt = Var(model.HP, model.CP, model.K, bounds=dt_bounds,\ doc="Approach between i and j in location k" ) model.dt_cu = Var(model.HP, bounds=dt_cu_bounds,\ doc="Approach between i and the cold utility" ) model.dt_hu = Var(model.CP, bounds=dt_hu_bounds,\ doc="Approach between j and the hot utility" ) # Log mean temperature differences raised to the beta-th power model.reclmtd_beta = Var(model.HP, model.CP, model.ST, bounds=reclmtd_beta_bounds,\ doc="Log mean temperature difference between hot stream i and cold stream j at stage k raised to the beta-th power") model.reclmtd_cu_beta = Var(model.HP, bounds=reclmtd_cu_beta_bounds,\ doc="Log mean temperature difference between hot stream i and cold utility raised to the beta-th power") model.reclmtd_hu_beta = Var(model.CP, bounds=reclmtd_hu_beta_bounds,\ doc="Log mean temperature difference between cold stream j and hot utility raised to the beta-th power") # Heat loads model.q = Var(model.HP, model.CP, model.ST, bounds=q_bounds, initialize = 0,\ doc="heat load between hot stream i and cold stream j at stage k" ) model.q_cu = Var(model.HP, bounds=q_cu_bounds, initialize = 0,\ doc="heat load between hot stream i and cold utility" ) model.q_hu = Var(model.CP, bounds=q_hu_bounds, initialize = 0,\ doc="heat load between cold stream j and hot utility" ) # Heat loads to the beta-th power model.q_beta = Var(model.HP, model.CP, model.ST, bounds=q_beta_bounds, initialize = 0,\ doc="heat load between hot stream i and cold stream j at stage k raised to the beta-th power" ) model.q_cu_beta = Var(model.HP, bounds=q_cu_beta_bounds, initialize = 0,\ doc="heat load between hot stream i and cold utility raised to the beta-th power" ) model.q_hu_beta = Var(model.CP, bounds=q_hu_beta_bounds, initialize = 0,\ doc="heat load between cold stream j and hot utility raised to the beta-th power" ) # Per stage temperatures model.th = Var(model.HP, model.K, bounds=th_bounds,\ doc="temperature of hot stream i at hot end of stage k" ) model.tc = Var(model.CP, model.K, bounds=tc_bounds,\ doc="temperature of cold stream j at hot end of stage k" ) # Binary variables model.z = Var(model.HP, model.CP, model.ST, initialize = 0, domain=Binary,\ doc="existence of the match between hot stream i and cold stream j at stage k" ) model.z_cu = Var(model.HP, initialize = 0, domain=Binary,\ doc="existence of the match between hot stream i and cold utility" ) model.z_hu = Var(model.CP, initialize = 0, domain=Binary,\ doc="existence of the match between cold stream j and hot utility" ) model.z_q_beta = Var(z_q_beta_index, domain=Binary) model.z_q_cu_beta = Var(z_q_cu_beta_index, domain=Binary) model.z_q_hu_beta = Var(z_q_hu_beta_index, domain=Binary) model.z_area_beta_q = Var(z_area_beta_q_index, domain=Binary) model.z_area_beta_q_cu = Var(z_area_beta_q_cu_index, domain=Binary) model.z_area_beta_q_hu = Var(z_area_beta_q_hu_index, domain=Binary) model.var_delta_reclmtd_beta = Var(z_area_beta_q_index, domain=NonNegativeReals) model.var_delta_reclmtd_cu_beta = Var(z_area_beta_q_cu_index, domain=NonNegativeReals) model.var_delta_reclmtd_hu_beta = Var(z_area_beta_q_hu_index, domain=NonNegativeReals) # New variables model.bh_in = Var(model.HP, model.CP, model.ST, bounds=bh_bounds) model.bh_out = Var(model.HP, model.CP, model.ST, bounds=bh_bounds) model.bc_in = Var(model.HP, model.CP, model.ST, bounds=bc_bounds) model.bc_out = Var(model.HP, model.CP, model.ST, bounds=bc_bounds) model.z_th = Var(z_th_index, domain=Binary) model.z_thx = Var(z_thx_index, domain=Binary) model.z_tc = Var(z_tc_index, domain=Binary) model.z_tcx = Var(z_tcx_index, domain=Binary) model.var_delta_fh = Var(var_delta_fh_index, domain=NonNegativeReals) model.var_delta_fhx = Var(var_delta_fhx_index, domain=NonNegativeReals) model.var_delta_fc = Var(var_delta_fc_index, domain=NonNegativeReals) model.var_delta_fcx = Var(var_delta_fcx_index, domain=NonNegativeReals)
def apply(self, **kwds): if __debug__ and logger.isEnabledFor(logging.DEBUG): #pragma:nocover logger.debug("Calling ConnectorExpander") instance = kwds['instance'] blockList = list(instance.block_data_objects(active=True)) noConnectors = True for b in blockList: if b.component_map(Connector): noConnectors = False break if noConnectors: return if __debug__ and logger.isEnabledFor(logging.DEBUG): #pragma:nocover logger.debug(" Connectors found!") # # At this point, there are connectors in the model, so we must # look for constraints that involve connectors and expand them. # #options = kwds['options'] #model = kwds['model'] # In general, blocks should be relatively self-contained, so we # should build the connectors from the "bottom up": blockList.reverse() # Expand each constraint involving a connector for block in blockList: if __debug__ and logger.isEnabledFor(logging.DEBUG): #pragma:nocover logger.debug(" block: " + block.name) CCC = {} for name, constraint in itertools.chain\ ( iteritems(block.component_map(Constraint)), iteritems(block.component_map(ConstraintList)) ): cList = [] CCC[name+'.expanded'] = cList for idx, c in iteritems(constraint._data): if __debug__ and logger.isEnabledFor(logging.DEBUG): #pragma:nocover logger.debug(" (looking at constraint %s[%s])", name, idx) connectors = [] self._gather_connectors(c.body, connectors) if len(connectors) == 0: continue if __debug__ and logger.isEnabledFor(logging.DEBUG): #pragma:nocover logger.debug(" (found connectors in constraint)") # Validate that all connectors match errors, ref, skip = self._validate_connectors(connectors) if errors: logger.error( ( "Connector mismatch: errors detected when " "constructing constraint %s\n " % (name + (idx and '[%s]' % idx or '')) ) + '\n '.join(reversed(errors)) ) raise ValueError( "Connector mismatch in constraint %s" % \ name + (idx and '[%s]' % idx or '')) if __debug__ and logger.isEnabledFor(logging.DEBUG): #pragma:nocover logger.debug(" (connectors valid)") # Fill in any empty connectors for conn in connectors: if conn.vars: continue for var in ref.vars: if var in skip: continue v = Var() block.add_component(conn.local_name + '.auto.' + var, v) conn.vars[var] = v v.construct() # OK - expand this constraint self._expand_constraint(block, name, idx, c, ref, skip, cList) # Now deactivate the original constraint c.deactivate() for name, exprs in iteritems(CCC): cList = ConstraintList() block.add_component( name, cList ) cList.construct() for expr in exprs: cList.add(expr) # Now, go back and implement VarList aggregators for block in blockList: for conn in itervalues(block.component_map(Connector)): for var, aggregator in iteritems(conn.aggregators): c = Constraint(expr=aggregator(block, var)) block.add_component( conn.local_name + '.' + var.local_name + '.aggregate', c) c.construct()
def build(self): """ Begin building model (pre-DAE transformation). Args: None Returns: None """ # Call UnitModel.build to build default attributes super(MBRData, self).build() # Set flow directions for the control volume blocks # Gas flows from 0 to 1, solid flows from 1 to 0 # An if statement is used here despite only one option to allow for # future extensions to other flow configurations if self.config.flow_type == "counter_current": set_direction_gas = FlowDirection.forward set_direction_solid = FlowDirection.backward # Set transformation scheme to be in the "opposite # direction" as flow. self.GAS_TRANSFORM_SCHEME = "BACKWARD" self.SOLID_TRANSFORM_SCHEME = "FORWARD" else: raise BurntToast("{} encountered unrecognized argument " "for flow type. Please contact the IDAES" " developers with this bug.".format(self.name)) # Set arguments for gas sides if homoogeneous reaction block if self.config.gas_phase_config.reaction_package is not None: has_rate_reaction_gas_phase = True else: has_rate_reaction_gas_phase = False # Set arguments for gas and solid sides if heterogeneous reaction block if self.config.solid_phase_config.reaction_package is not None: has_rate_reaction_solid_phase = True has_mass_transfer_gas_phase = True else: has_rate_reaction_solid_phase = False has_mass_transfer_gas_phase = False # Set heat transfer terms if self.config.energy_balance_type != EnergyBalanceType.none: has_heat_transfer = True else: has_heat_transfer = False # Set heat of reaction terms if (self.config.energy_balance_type != EnergyBalanceType.none and self.config.gas_phase_config.reaction_package is not None): has_heat_of_reaction_gas_phase = True else: has_heat_of_reaction_gas_phase = False if (self.config.energy_balance_type != EnergyBalanceType.none and self.config.solid_phase_config.reaction_package is not None): has_heat_of_reaction_solid_phase = True else: has_heat_of_reaction_solid_phase = False # Create two different length domains; one for each phase. # Add them to this block so I can use one of them to index # all the variables on this block. # I can then call discretization on this block, which will # discretize the variables on the control volume blocks. self.solid_length_domain = ContinuousSet( bounds=(0.0, 1.0), initialize=self.config.length_domain_set, doc="Normalized length domain", ) self.gas_length_domain = ContinuousSet( bounds=(0.0, 1.0), initialize=self.config.length_domain_set, doc="Normalized length domain", ) self.bed_height = Var(domain=Reals, initialize=1, doc="Bed length [m]") super(_BlockData, self).__setattr__( 'length_domain', self.solid_length_domain, ) # ========================================================================= """ Build Control volume 1D for gas phase and populate gas control volume""" self.gas_phase = ControlVolume1DBlock( default={ "transformation_method": self.config.transformation_method, #"transformation_scheme": self.config.transformation_scheme, "transformation_scheme": self.GAS_TRANSFORM_SCHEME, "finite_elements": self.config.finite_elements, "collocation_points": self.config.collocation_points, "dynamic": self.config.dynamic, "has_holdup": self.config.has_holdup, "area_definition": DistributedVars.variant, "property_package": self.config.gas_phase_config.property_package, "property_package_args": self.config.gas_phase_config.property_package_args, "reaction_package": self.config.gas_phase_config.reaction_package, "reaction_package_args": self.config.gas_phase_config.reaction_package_args }) # Pass gas_length_domain to the gas phase control volume # Note that length_domain_set is redundant as the set is # already initialized. self.gas_phase.add_geometry( length_domain=self.gas_length_domain, length_domain_set=self.config.length_domain_set, flow_direction=set_direction_gas, ) self.gas_phase.add_state_blocks(information_flow=set_direction_gas, has_phase_equilibrium=False) if self.config.gas_phase_config.reaction_package is not None: self.gas_phase.add_reaction_blocks( has_equilibrium=self.config.gas_phase_config. has_equilibrium_reactions) self.gas_phase.add_material_balances( balance_type=self.config.material_balance_type, has_phase_equilibrium=False, has_mass_transfer=has_mass_transfer_gas_phase, has_rate_reactions=has_rate_reaction_gas_phase) self.gas_phase.add_energy_balances( balance_type=self.config.energy_balance_type, has_heat_transfer=has_heat_transfer, has_heat_of_reaction=has_heat_of_reaction_gas_phase) self.gas_phase.add_momentum_balances( balance_type=self.config.momentum_balance_type, has_pressure_change=self.config.has_pressure_change) # ========================================================================= """ Build Control volume 1D for solid phase and populate solid control volume""" # Set argument for heterogeneous reaction block self.solid_phase = ControlVolume1DBlock( default={ "transformation_method": self.config.transformation_method, "transformation_scheme": self.SOLID_TRANSFORM_SCHEME, "finite_elements": self.config.finite_elements, "collocation_points": self.config.collocation_points, # ^ These arguments have no effect as the transformation # is applied in this class. "dynamic": self.config.dynamic, "has_holdup": self.config.has_holdup, "area_definition": DistributedVars.variant, "property_package": self.config.solid_phase_config.property_package, "property_package_args": self.config.solid_phase_config.property_package_args, "reaction_package": self.config.solid_phase_config.reaction_package, "reaction_package_args": self.config.solid_phase_config.reaction_package_args }) # Same comment as made for the gas phase. # Pass in the set we've constructed for this purpose. self.solid_phase.add_geometry( length_domain=self.solid_length_domain, length_domain_set=self.config.length_domain_set, flow_direction=set_direction_solid) # These constraints no longer are created by make_performance, # So I make them here... # Length of gas side, and solid side @self.Constraint(doc="Gas side length") def gas_phase_length(b): return (b.gas_phase.length == b.bed_height) @self.Constraint(doc="Solid side length") def solid_phase_length(b): return (b.solid_phase.length == b.bed_height) # Many other methods of the MBR base class actually rely on this # attribute, so here I slap on a reference. The particular set # I use shouldn't matter, as long as the derivatives are constructed # wrt the correct set. self.solid_phase.add_state_blocks(information_flow=set_direction_solid, has_phase_equilibrium=False) if self.config.solid_phase_config.reaction_package is not None: # TODO - a generalization of the heterogeneous reaction block # The heterogeneous reaction block does not use the # add_reaction_blocks in control volumes as control volumes are # currently setup to handle only homogeneous reaction properties. # Thus appending the heterogeneous reaction block to the # solid state block is currently hard coded here. tmp_dict = dict( **self.config.solid_phase_config.reaction_package_args) tmp_dict["gas_state_block"] = self.gas_phase.properties tmp_dict["solid_state_block"] = self.solid_phase.properties tmp_dict["has_equilibrium"] = ( self.config.solid_phase_config.has_equilibrium_reactions) tmp_dict["parameters"] = ( self.config.solid_phase_config.reaction_package) self.solid_phase.reactions = ( self.config.solid_phase_config.reaction_package. reaction_block_class( self.flowsheet().config.time, self.length_domain, doc="Reaction properties in control volume", default=tmp_dict)) self.solid_phase.add_material_balances( balance_type=self.config.material_balance_type, has_phase_equilibrium=False, has_mass_transfer=False, has_rate_reactions=has_rate_reaction_solid_phase) self.solid_phase.add_energy_balances( balance_type=self.config.energy_balance_type, has_heat_transfer=has_heat_transfer, has_heat_of_reaction=has_heat_of_reaction_solid_phase) self.solid_phase.add_momentum_balances( balance_type=MomentumBalanceType.none, has_pressure_change=False) # ========================================================================= """ Add ports""" # Add Ports for gas side self.add_inlet_port(name="gas_inlet", block=self.gas_phase) self.add_outlet_port(name="gas_outlet", block=self.gas_phase) # Add Ports for solid side self.add_inlet_port(name="solid_inlet", block=self.solid_phase) self.add_outlet_port(name="solid_outlet", block=self.solid_phase) # ========================================================================= """ Add performace equation method""" self._apply_transformation() self._make_performance()