def initialize(blk, state_args=None, outlvl=idaeslog.NOTSET, solver=None, optarg=None): ''' Downcomer initialization routine. Keyword Arguments: state_args : a dict of arguments to be passed to the property package(s) for the control_volume of the model to provide an initial state for initialization (see documentation of the specific property package) (default = None). outlvl : sets output level of initialisation routine optarg : solver options dictionary object (default=None, use default solver options) solver : str indicating which solver to use during initialization (default = None, use default solver) Returns: None ''' init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="unit") solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="unit") # Create solver opt = get_solver(solver, optarg) init_log.info_low("Starting initialization...") flags = blk.control_volume.initialize( outlvl=outlvl+1, optarg=optarg, solver=solver, state_args=state_args, ) init_log.info_high("Initialization Step 1 Complete.") # make sure 0 DoF if degrees_of_freedom(blk) != 0: raise ConfigurationError( "Incorrect degrees of freedom when initializing {}: dof = {}".format( blk.name, degrees_of_freedom(blk))) # Fix outlet pressure for t in blk.flowsheet().config.time: blk.control_volume.properties_out[t].pressure.fix( value(blk.control_volume.properties_in[t].pressure) ) blk.pressure_change_total_eqn.deactivate() with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = opt.solve(blk, tee=slc.tee) init_log.info_high( "Initialization Step 2 {}.".format(idaeslog.condition(res)) ) # Unfix outlet enthalpy and pressure for t in blk.flowsheet().config.time: blk.control_volume.properties_out[t].pressure.unfix() blk.pressure_change_total_eqn.activate() with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = opt.solve(blk, tee=slc.tee) init_log.info_high( "Initialization Step 3 {}.".format(idaeslog.condition(res)) ) blk.control_volume.release_state(flags, outlvl+1) init_log.info("Initialization Complete.")
def initialize_by_element_in_range(model, time, t_start, t_end, time_linking_vars=[], dae_vars=[], max_linking_range=0, **kwargs): """Function for solving a square model, time element-by-time element, between specified start and end times. Args: model : Flowsheet model to solve t_start : Beginning of timespan over which to solve t_end : End of timespan over which to solve Kwargs: solver : Solver option used to solve portions of the square model outlvl : idaes.logger output level """ solver = kwargs.pop('solver', SolverFactory('ipopt')) outlvl = kwargs.pop('outlvl', idaeslog.NOTSET) init_log = idaeslog.getInitLogger('nmpc', outlvl) solver_log = idaeslog.getSolveLogger('nmpc', outlvl) solve_initial_conditions = kwargs.pop('solve_initial_conditions', False) #TODO: Move to docstring # Variables that will be fixed for time points outside the finite element # when constraints for a finite element are activated. # For a "normal" process, these should just be differential variables # (and maybe derivative variables). For a process with a (PID) controller, # these should also include variables used by the controller. # If these variables are not specified, # Timespan over which these variables will be fixed, counting backwards # from the first time point in the finite element (which will always be # fixed) # Should I specify max_linking_range as an integer number of finite # elements, an integer number of time points, or a float in actual time # units? Go with latter for now. assert t_start in time.get_finite_elements() assert t_end in time.get_finite_elements() #assert degrees_of_freedom(model) == 0 # No need to check dof here as we will check right before each solve #dae_vars = kwargs.pop('dae_vars', []) if not dae_vars: scalar_vars, dae_vars = flatten_dae_components(model, time, ctype=Var) for var in scalar_vars: var.fix() deactivate_constraints_unindexed_by(model, time) ncp = time.get_discretization_info()['ncp'] fe_in_range = [ i for i, fe in enumerate(time.get_finite_elements()) if fe >= t_start and fe <= t_end ] t_in_range = [t for t in time if t >= t_start and t <= t_end] fe_in_range.pop(0) n_fe_in_range = len(fe_in_range) was_originally_active = get_activity_dict(model) was_originally_fixed = get_fixed_dict(model) # Deactivate model if not solve_initial_conditions: time_list = [t for t in time] deactivated = deactivate_model_at(model, time, time_list, outlvl=idaeslog.ERROR) else: time_list = [t for t in time if t != time.first()] deactivated = deactivate_model_at(model, time, time_list, outlvl=idaeslog.ERROR) assert degrees_of_freedom(model) == 0 with idaeslog.solver_log(solver_log, level=idaeslog.DEBUG) as slc: results = solver.solve(model, tee=slc.tee) if results.solver.termination_condition == TerminationCondition.optimal: pass else: raise ValueError( 'Failed to solve for consistent initial conditions.') deactivated[time.first()] = deactivate_model_at( model, time, time.first(), outlvl=idaeslog.ERROR)[time.first()] # "Integration" loop for i in fe_in_range: t_prev = time.at((i - 1) * ncp + 1) fe = [time.at(k) for k in range((i - 1) * ncp + 2, i * ncp + 2)] con_list = [] for t in fe: # These will be fixed vars in constraints at t # Probably not necessary to record at what t # they occur for comp in deactivated[t]: if was_originally_active[id(comp)]: comp.activate() if not time_linking_vars: if isinstance(comp, _ConstraintData): con_list.append(comp) elif isinstance(comp, _BlockData): # Active here should be independent of whether block # was active con_list.extend( list( comp.component_data_objects(Constraint, active=True))) if not time_linking_vars: fixed_vars = [] for con in con_list: for var in identify_variables(con.expr, include_fixed=False): # use var_locator/ComponentMap to get index somehow t_idx = get_implicit_index_of_set(var, time) if t_idx is None: assert not is_in_block_indexed_by(var, time) continue if t_idx <= t_prev: fixed_vars.append(var) var.fix() else: fixed_vars = [] time_range = [ t for t in time if t_prev - t <= max_linking_range and t <= t_prev ] time_range = [t_prev] for _slice in time_linking_vars: for t in time_range: #if not _slice[t].fixed: _slice[t].fix() fixed_vars.append(_slice[t]) # Here I assume that the only variables that can appear in # constraints at a different (later) time index are derivatives # and differential variables (they do so in the discretization # equations) and that they only participate at t_prev. # # This is not the case for, say, PID controllers, in which case # I should pass in a list of "complicating variables," then fix # them at all time points outside the finite element. # # Alternative solution is to identify_variables in each constraint # that is activated and fix those belonging to a previous finite # element. (Should not encounter variables belonging to a future # finite element.) # ^ This option is easier, less efficient # # In either case need to record whether variable was previously fixed # so I know if I should unfix it or not. for t in fe: for _slice in dae_vars: if not _slice[t].fixed: # Fixed DAE variables are time-dependent disturbances, # whose values should not be altered by this function. _slice[t].set_value(_slice[t_prev].value) assert degrees_of_freedom(model) == 0 with idaeslog.solver_log(solver_log, level=idaeslog.DEBUG) as slc: results = solver.solve(model, tee=slc.tee) if results.solver.termination_condition == TerminationCondition.optimal: pass else: raise ValueError('Failed to solve for finite element %s' % i) for t in fe: for comp in deactivated[t]: comp.deactivate() for var in fixed_vars: if not was_originally_fixed[id(var)]: var.unfix() for t in time: for comp in deactivated[t]: if was_originally_active[id(comp)]: comp.activate()
def initialize( m, outlvl=idaeslog.NOTSET, optarg={ "tol": 1e-6, "max_iter": 40, }, ): """Initialize unit models""" init_log = idaeslog.getInitLogger(m.name, outlvl, tag="flowsheet") solve_log = idaeslog.getSolveLogger(m.name, outlvl, tag="flowsheet") solver = get_solver() solver.options = optarg init_log.info_low("Starting initialization...") if not os.path.exists("subcritical_boiler_init.json.gz"): # 10 Waterwalls, initial guess for specific simulation m.fs.Waterwalls[1].heat_fireside[:].fix(2.3e7) m.fs.Waterwalls[2].heat_fireside[:].fix(1.5e7) m.fs.Waterwalls[3].heat_fireside[:].fix(6.8e6) m.fs.Waterwalls[4].heat_fireside[:].fix(1.2e7) m.fs.Waterwalls[5].heat_fireside[:].fix(1.2e7) m.fs.Waterwalls[6].heat_fireside[:].fix(1.2e7) m.fs.Waterwalls[7].heat_fireside[:].fix(1.0e7) m.fs.Waterwalls[8].heat_fireside[:].fix(9.9e6) m.fs.Waterwalls[9].heat_fireside[:].fix(2.1) m.fs.Waterwalls[10].heat_fireside[:].fix(2.0e7) state_args_water_steam = { 'flow_mol': 199470.7831, # mol/s 'pressure': 10903981.9107, # Pa 'enth_mol': 26585.3483 } # j/mol state_args_feedwater = { 'flow_mol': 4630.6098, # mol/s 'pressure': 10903981.9107, # Pa 'enth_mol': 22723.907 } # j/mol m.fs.drum.initialize( outlvl=outlvl, optarg=solver.options, state_args_water_steam=state_args_water_steam, state_args_feedwater=state_args_feedwater, ) m.fs.downcomer.inlet.flow_mol[:].fix( m.fs.drum.liquid_outlet.flow_mol[0].value) m.fs.downcomer.inlet.pressure[:].fix( m.fs.drum.liquid_outlet.pressure[0].value) m.fs.downcomer.inlet.enth_mol[:].fix( m.fs.drum.liquid_outlet.enth_mol[0].value) m.fs.downcomer.initialize( state_args={ "flow_mol": m.fs.drum.liquid_outlet.flow_mol[0].value, "pressure": m.fs.drum.liquid_outlet.pressure[0].value, "enth_mol": m.fs.drum.liquid_outlet.enth_mol[0].value, }, outlvl=outlvl, optarg=solver.options, ) m.fs.Waterwalls[1].inlet.flow_mol[:].fix( m.fs.downcomer.outlet.flow_mol[0].value) m.fs.Waterwalls[1].inlet.pressure[:].fix( m.fs.downcomer.outlet.pressure[0].value) m.fs.Waterwalls[1].inlet.enth_mol[:].fix( m.fs.downcomer.outlet.enth_mol[0].value) m.fs.Waterwalls[1].initialize( state_args={ "flow_mol": m.fs.Waterwalls[1].inlet.flow_mol[0].value, "pressure": m.fs.Waterwalls[1].inlet.pressure[0].value, "enth_mol": m.fs.Waterwalls[1].inlet.enth_mol[0].value, }, outlvl=6, optarg=solver.options, ) for i in range(2, 11): m.fs.Waterwalls[i].inlet.flow_mol[:].fix( m.fs.Waterwalls[i - 1].outlet.flow_mol[0].value) m.fs.Waterwalls[i].inlet.pressure[:].fix( m.fs.Waterwalls[i - 1].outlet.pressure[0].value) m.fs.Waterwalls[i].inlet.enth_mol[:].fix( m.fs.Waterwalls[i - 1].outlet.enth_mol[0].value) m.fs.Waterwalls[i].initialize( state_args={ "flow_mol": m.fs.Waterwalls[i - 1].outlet.flow_mol[0].value, "pressure": m.fs.Waterwalls[i - 1].outlet.pressure[0].value, "enth_mol": m.fs.Waterwalls[i - 1].outlet.enth_mol[0].value, }, outlvl=6, optarg=solver.options, ) m.fs.drum.feedwater_inlet.flow_mol[:].fix() m.fs.drum.feedwater_inlet.pressure[:].unfix() m.fs.drum.feedwater_inlet.enth_mol[:].fix() m.fs.downcomer.inlet.flow_mol[:].unfix() m.fs.downcomer.inlet.pressure[:].unfix() m.fs.downcomer.inlet.enth_mol[:].unfix() print('solving full-space problem') for i in m.fs.ww_zones: m.fs.Waterwalls[i].inlet.flow_mol[:].unfix() m.fs.Waterwalls[i].inlet.pressure[:].unfix() m.fs.Waterwalls[i].inlet.enth_mol[:].unfix() df = degrees_of_freedom(m) if df != 0: raise ValueError("Check degrees of freedom: {}".format(df)) with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: print('solving full-space problem') res = solver.solve(m, tee=slc.tee) init_log.info_low("Initialization Complete: {}".format( idaeslog.condition(res))) ms.to_json(m, fname="subcritical_boiler_init.json.gz") else: print('\n\nInitializing from json file') ms.from_json(m, fname="subcritical_boiler_init.json.gz") return m
def test_initialize(): """Make a turbine model and make sure it doesn't throw exception""" m = build_turbine_for_run_test() turb = m.fs.turb # Set the inlet of the turbine p = 2.4233e7 hin = pyo.value(iapws95.htpx(T=880*pyo.units.K, P=p*pyo.units.Pa)) m.fs.turb.inlet_split.inlet.enth_mol[0].fix(hin) m.fs.turb.inlet_split.inlet.flow_mol[0].fix(26000) m.fs.turb.inlet_split.inlet.pressure[0].fix(p) # Set the inlet of the ip section, which is disconnected # here to insert reheater p = 7.802e+06 hin = pyo.value(iapws95.htpx(T=880*pyo.units.K, P=p*pyo.units.Pa)) m.fs.turb.ip_stages[1].inlet.enth_mol[0].value = hin m.fs.turb.ip_stages[1].inlet.flow_mol[0].value = 25220.0 m.fs.turb.ip_stages[1].inlet.pressure[0].value = p for i, s in turb.hp_stages.items(): s.ratioP[:] = 0.88 s.efficiency_isentropic[:] = 0.9 for i, s in turb.ip_stages.items(): s.ratioP[:] = 0.85 s.efficiency_isentropic[:] = 0.9 for i, s in turb.lp_stages.items(): s.ratioP[:] = 0.82 s.efficiency_isentropic[:] = 0.9 turb.hp_split[4].split_fraction[0,"outlet_2"].fix(0.03) turb.hp_split[7].split_fraction[0,"outlet_2"].fix(0.03) turb.ip_split[5].split_fraction[0,"outlet_2"].fix(0.04) turb.ip_split[14].split_fraction[0,"outlet_2"].fix(0.04) turb.ip_split[14].split_fraction[0,"outlet_3"].fix(0.15) turb.lp_split[4].split_fraction[0,"outlet_2"].fix(0.04) turb.lp_split[7].split_fraction[0,"outlet_2"].fix(0.04) turb.lp_split[9].split_fraction[0,"outlet_2"].fix(0.04) turb.lp_split[11].split_fraction[0,"outlet_2"].fix(0.04) # Congiure with reheater for a full test turb.ip_stages[1].inlet.fix() for i in turb.throttle_valve: turb.throttle_valve[i].Cv.fix() turb.throttle_valve[i].valve_opening.fix() turb.inlet_split.inlet.flow_mol.unfix() turb.inlet_mix.use_equal_pressure_constraint() iscale.calculate_scaling_factors(m) turb.initialize(outlvl=1) turb.ip_stages[1].inlet.unfix() for t in m.fs.time: m.fs.reheat.inlet.flow_mol[t].value = \ pyo.value(turb.hp_split[7].outlet_1_state[t].flow_mol) m.fs.reheat.inlet.enth_mol[t].value = \ pyo.value(turb.hp_split[7].outlet_1_state[t].enth_mol) m.fs.reheat.inlet.pressure[t].value = \ pyo.value(turb.hp_split[7].outlet_1_state[t].pressure) m.fs.reheat.initialize(outlvl=4) def reheat_T_rule(b, t): return m.fs.reheat.control_volume.properties_out[t].temperature == 880 m.fs.reheat.temperature_out_equation = pyo.Constraint( m.fs.reheat.flowsheet().config.time, rule=reheat_T_rule) pyo.TransformationFactory("network.expand_arcs").apply_to(m) m.fs.turb.outlet_stage.control_volume.properties_out[0].pressure.fix() assert degrees_of_freedom(m)==0 solver.solve(m, tee=True) eq_cons = activated_equalities_generator(m) for c in eq_cons: assert abs(c.body() - c.lower) < 1e-4 return m
def test_dof(self, sapon): assert degrees_of_freedom(sapon) == 0
def test_oc_conv(self): m = self._make_model() rxn_block = m.fs.reaction_block n_scen = 3 mols = pyunits.mol / pyunits.s kgs = pyunits.kg / pyunits.s K = pyunits.K bar = pyunits.bar state_values = { "gas_state.flow_mol": [1.0 * mols] * n_scen, "solid_state.flow_mass": [1.0 * kgs] * n_scen, "gas_state.temperature": [1273.0 * K] * n_scen, "solid_state.temperature": [1273.0 * K] * n_scen, "gas_state.pressure": [1.0 * bar] * n_scen, "solid_state.particle_porosity": [0.27] * n_scen, "gas_state.mole_frac_comp[O2]": [0.25] * n_scen, "gas_state.mole_frac_comp[N2]": [0.25] * n_scen, "gas_state.mole_frac_comp[H2O]": [0.25] * n_scen, "gas_state.mole_frac_comp[CO2]": [0.25] * n_scen, "solid_state.mass_frac_comp[Fe2O3]": [2 / 3, 0.0, 1 / 3], "solid_state.mass_frac_comp[Fe3O4]": [0.0, 2 / 3, 1 / 3], "solid_state.mass_frac_comp[Al2O3]": [1 / 3] * n_scen, } state_values = ComponentMap((m.fs.find_component(name), values) for name, values in state_values.items()) target_values = { "reaction_block.OC_conv": [ 1.0, 0.0, 0.4915, ], "reaction_block.OC_conv_temp": [ 2.005e-4, 1.0, 0.6371, ], } target_values = ComponentMap((m.fs.find_component(name), values) for name, values in target_values.items()) assert degrees_of_freedom(m.fs) == 0 param_sweeper = ParamSweeper(n_scen, state_values, output_values=target_values) with param_sweeper: for inputs, outputs in param_sweeper: solve_strongly_connected_components(m.fs) # Make sure property equalites have been converged assert number_large_residuals(m.fs, tol=1e-8) == 0 # Sanity checks that inputs are properly set for var, val in inputs.items(): val = value(pyunits.convert(val, var.get_units())) assert var.value == pytest.approx(value(val), abs=1e-3) # Make sure properties have been calculated as expected for var, val in outputs.items(): val = value(pyunits.convert(val, var.get_units())) assert var.value == pytest.approx(value(val), abs=1e-3)
def initialize(self, state_args={}, outlvl=0, solver='ipopt', optarg={ 'tol': 1e-6, 'max_iter': 30 }): """ Initialize the turbine stage model. This deactivates the specialized constraints, then does the isentropic turbine initialization, then reactivates the constraints and solves. Args: state_args (dict): Initial state for property initialization outlvl (int): Amount of output (0 to 3) 0 is lowest solver (str): Solver to use for initialization optarg (dict): Solver arguments dictionary """ stee = True if outlvl >= 3 else False # sp is what to save to make sure state after init is same as the start # saves value, fixed, and active state, doesn't load originally free # values, this makes sure original problem spec is same but initializes # the values of free vars sp = StoreSpec.value_isfixed_isactive(only_fixed=True) istate = to_json(self, return_dict=True, wts=sp) self.deltaP[:].unfix() self.ratioP[:].unfix() # fix inlet and free outlet for t in self.flowsheet().config.time: for k, v in self.inlet.vars.items(): v[t].fix() for k, v in self.outlet.vars.items(): v[t].unfix() # to calculate outlet pressure Pout = self.outlet.pressure[t] Pin = self.inlet.pressure[t] if self.deltaP[t].value is not None: prdp = value((self.deltaP[t] - Pin) / Pin) else: prdp = -100 # crazy number to say don't use deltaP as guess if value(Pout / Pin) > 1 or value(Pout / Pin) < 0.0: if value(self.ratioP[t]) <= 1 and value(self.ratioP[t]) >= 0: Pout.value = value(Pin * self.ratioP[t]) elif prdp <= 1 and prdp >= 0: Pout.value = value(prdp * Pin) else: Pout.value = value(Pin * 0.95) self.deltaP[t] = value(Pout - Pin) self.ratioP[t] = value(Pout / Pin) # Make sure the initialization problem has no degrees of freedom # This shouldn't happen here unless there is a bug in this dof = degrees_of_freedom(self) try: assert (dof == 0) except: _log.exception("degrees_of_freedom = {}".format(dof)) raise # one bad thing about reusing this is that the log messages aren't # really compatible with being nested inside another initialization super().initialize(state_args=state_args, outlvl=outlvl, solver=solver, optarg=optarg) # reload original spec from_json(self, sd=istate, wts=sp)
def test_CV1D_w_inherent_rxns_comp_total(self, frame): frame.fs.cv = ControlVolume1DBlock( default={ "property_package": frame.fs.params, "transformation_method": "dae.finite_difference", "transformation_scheme": "BACKWARD", "finite_elements": 2 }) frame.fs.cv.add_geometry() frame.fs.cv.add_state_blocks(has_phase_equilibrium=False) frame.fs.cv.add_material_balances( balance_type=MaterialBalanceType.componentTotal) frame.fs.cv.add_energy_balances() frame.fs.cv.add_momentum_balances() frame.fs.cv.apply_transformation() frame.fs.cv.properties[0, 0].flow_mol.fix(100) frame.fs.cv.properties[0, 0].mole_frac_comp.fix(0.25) frame.fs.cv.properties[0, 0].temperature.fix(350) frame.fs.cv.properties[0, 0].pressure.fix(101325) frame.fs.cv.area.fix(1) frame.fs.cv.length.fix(1) assert (degrees_of_freedom(frame)) == 0 frame.fs.cv.initialize() solver = get_solver() results = solver.solve(frame) assert results.solver.termination_condition == \ TerminationCondition.optimal assert results.solver.status == SolverStatus.ok assert value(frame.fs.cv.properties[0, 1].k_eq["e1"]) == 2 assert (value( frame.fs.cv.properties[0, 1].k_eq["e1"]) == pytest.approx( value(frame.fs.cv.properties[0, 1].mole_frac_comp["b"] / frame.fs.cv.properties[0, 1].mole_frac_comp["a"]), rel=1e-5)) assert (value( frame.fs.cv.properties[0, 1].mole_frac_comp["a"]) == pytest.approx( 1 / 6, rel=1e-5)) assert (value( frame.fs.cv.properties[0, 1].mole_frac_comp["b"]) == pytest.approx( 1 / 3, rel=1e-5)) assert (value( frame.fs.cv.properties[0, 1].mole_frac_comp["c"]) == pytest.approx( 1 / 4, rel=1e-5)) assert (value( frame.fs.cv.properties[0, 1].mole_frac_comp["d"]) == pytest.approx( 1 / 4, rel=1e-5)) assert (value(frame.fs.cv.properties[0, 1].flow_mol) == pytest.approx( 100, rel=1e-5)) assert (value(frame.fs.cv.properties[0, 1].temperature) == pytest.approx( 350, rel=1e-5)) assert (value(frame.fs.cv.properties[0, 1].pressure) == pytest.approx( 101325, rel=1e-5))
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) 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)
def report(self, time_point=0, dof=False, ostream=None, prefix=""): time_point = float(time_point) if ostream is None: ostream = sys.stdout # Get DoF and model stats if dof: dof_stat = degrees_of_freedom(self) nv = number_variables(self) nc = number_activated_constraints(self) nb = number_activated_blocks(self) # Get components to report in performance section performance = self._get_performance_contents(time_point=time_point) # Get stream table stream_table = self._get_stream_table_contents(time_point=time_point) # Set model type output if hasattr(self, "is_flowsheet") and self.is_flowsheet: model_type = "Flowsheet" else: model_type = "Unit" # Write output max_str_length = 84 tab = " " * 4 ostream.write("\n" + "=" * max_str_length + "\n") lead_str = f"{prefix}{model_type} : {self.name}" trail_str = f"Time: {time_point}" mid_str = " " * (max_str_length - len(lead_str) - len(trail_str)) ostream.write(lead_str + mid_str + trail_str) if dof: ostream.write("\n" + "=" * max_str_length + "\n") ostream.write(f"{prefix}{tab}Local Degrees of Freedom: {dof_stat}") ostream.write('\n') ostream.write(f"{prefix}{tab}Total Variables: {nv}{tab}" f"Activated Constraints: {nc}{tab}" f"Activated Blocks: {nb}") if performance is not None: ostream.write("\n" + "-" * max_str_length + "\n") ostream.write(f"{prefix}{tab}Unit Performance") ostream.write("\n" * 2) if "vars" in performance.keys() and len(performance["vars"]) > 0: ostream.write(f"{prefix}{tab}Variables: \n\n") tabular_writer( ostream, prefix + tab, ((k, v) for k, v in performance["vars"].items()), ("Value", "Fixed", "Bounds"), lambda k, v: ["{:#.5g}".format(value(v)), v.fixed, v.bounds]) if "exprs" in performance.keys() and len(performance["exprs"]) > 0: ostream.write("\n") ostream.write(f"{prefix}{tab}Expressions: \n\n") tabular_writer(ostream, prefix + tab, ((k, v) for k, v in performance["exprs"].items()), ("Value", ), lambda k, v: ["{:#.5g}".format(value(v))]) if ("params" in performance.keys() and len(performance["params"]) > 0): ostream.write("\n") ostream.write(f"{prefix}{tab}Parameters: \n\n") tabular_writer(ostream, prefix + tab, ((k, v) for k, v in performance["params"].items()), ("Value", "Mutable"), lambda k, v: [value(v), not v.is_constant()]) if stream_table is not None: ostream.write("\n" + "-" * max_str_length + "\n") ostream.write(f"{prefix}{tab}Stream Table") ostream.write('\n') ostream.write( textwrap.indent(stream_table_dataframe_to_string(stream_table), prefix + tab)) ostream.write("\n" + "=" * max_str_length + "\n")
def test_dof(self, frame): m = frame assert (degrees_of_freedom(m) == 0)
def initialize(self, state_args_feed=None, state_args_liq=None, state_args_vap=None, solver=None, optarg=None, outlvl=idaeslog.NOTSET): # TODO: # 1. Check initialization for dynamic mode. Currently not supported. # 2. Handle unfixed side split fraction vars # 3. Better logic to handle and fix state vars. init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit") solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit") init_log.info("Begin initialization.") if solver is None: init_log.warning("Solver not provided. Default solver(ipopt) " " being used for initialization.") solver = get_default_solver() if self.config.has_liquid_side_draw: if not self.liq_side_sf.fixed: raise ConfigurationError( "Liquid side draw split fraction not fixed but " "has_liquid_side_draw set to True.") if self.config.has_vapor_side_draw: if not self.vap_side_sf.fixed: raise ConfigurationError( "Vapor side draw split fraction not fixed but " "has_vapor_side_draw set to True.") # Create initial guess if not provided by using current values if self.config.is_feed_tray and state_args_feed is None: state_args_feed = {} state_args_liq = {} state_args_vap = {} state_dict = (self.properties_in_feed[ self.flowsheet().config.time.first()].define_port_members()) for k in state_dict.keys(): if "flow" in k: if state_dict[k].is_indexed(): state_args_feed[k] = {} state_args_liq[k] = {} state_args_vap[k] = {} for m in state_dict[k].keys(): state_args_feed[k][m] = \ state_dict[k][m].value state_args_liq[k][m] = \ 0.1 * state_dict[k][m].value state_args_vap[k][m] = \ 0.1 * state_dict[k][m].value else: state_args_feed[k] = state_dict[k].value state_args_liq[k] = 0.1 * state_dict[k].value state_args_vap[k] = 0.1 * state_dict[k].value else: if state_dict[k].is_indexed(): state_args_feed[k] = {} state_args_liq[k] = {} state_args_vap[k] = {} for m in state_dict[k].keys(): state_args_feed[k][m] = \ state_dict[k][m].value state_args_liq[k][m] = \ state_dict[k][m].value state_args_vap[k][m] = \ state_dict[k][m].value else: state_args_feed[k] = state_dict[k].value state_args_liq[k] = state_dict[k].value state_args_vap[k] = state_dict[k].value # Create initial guess if not provided by using current values if not self.config.is_feed_tray and state_args_liq is None: state_args_liq = {} state_dict = (self.properties_in_liq[ self.flowsheet().config.time.first()].define_port_members()) for k in state_dict.keys(): if state_dict[k].is_indexed(): state_args_liq[k] = {} for m in state_dict[k].keys(): state_args_liq[k][m] = state_dict[k][m].value else: state_args_liq[k] = state_dict[k].value # Create initial guess if not provided by using current values if not self.config.is_feed_tray and state_args_vap is None: state_args_vap = {} state_dict = (self.properties_in_vap[ self.flowsheet().config.time.first()].define_port_members()) for k in state_dict.keys(): if state_dict[k].is_indexed(): state_args_vap[k] = {} for m in state_dict[k].keys(): state_args_vap[k][m] = state_dict[k][m].value else: state_args_vap[k] = state_dict[k].value if self.config.is_feed_tray: feed_flags = self.properties_in_feed.initialize( outlvl=outlvl, solver=solver, optarg=optarg, hold_state=True, state_args=state_args_feed, state_vars_fixed=False) liq_in_flags = self.properties_in_liq. \ initialize(outlvl=outlvl, solver=solver, optarg=optarg, hold_state=True, state_args=state_args_liq, state_vars_fixed=False) vap_in_flags = self.properties_in_vap. \ initialize(outlvl=outlvl, solver=solver, optarg=optarg, hold_state=True, state_args=state_args_vap, state_vars_fixed=False) # state args to initialize the mixed outlet state block state_args_mixed = {} if self.config.is_feed_tray: # if feed tray, initialize the mixed state block at # the same condition. state_args_mixed = state_args_feed else: # if not feed tray, initialize mixed state block at average of # vap/liq inlets except pressure. While this is crude, it # will work for most combination of state vars. state_dict = (self.properties_in_liq[ self.flowsheet().config.time.first()].define_port_members()) for k in state_dict.keys(): if k == "pressure": # Take the lowest pressure and this is the liq inlet state_args_mixed[k] = self.properties_in_liq[0].\ component(state_dict[k].local_name).value elif state_dict[k].is_indexed(): state_args_mixed[k] = {} for m in state_dict[k].keys(): state_args_mixed[k][m] = \ 0.5 * (self.properties_in_liq[0]. component(state_dict[k].local_name)[m]. value + self.properties_in_vap[0]. component(state_dict[k].local_name)[m]. value) else: state_args_mixed[k] = \ 0.5 * (self.properties_in_liq[0]. component(state_dict[k].local_name).value + self.properties_in_vap[0]. component(state_dict[k].local_name).value) # Initialize the mixed outlet state block self.properties_out. \ initialize(outlvl=outlvl, solver=solver, optarg=optarg, hold_state=False, state_args=state_args_mixed, state_vars_fixed=False) # Deactivate energy balance self.enthalpy_mixing_equations.deactivate() # Try fixing the outlet temperature if else pass # NOTE: if passed then there would probably be a degree of freedom try: self.properties_out[:].temperature.\ fix(state_args_mixed["temperature"]) except AttributeError: init_log.warning("Trying to fix outlet temperature " "during initialization but temperature attribute " "unavailable in the state block. Initialization " "proceeding with a potential degree of freedom.") # Deactivate pressure balance self.pressure_drop_equation.deactivate() # Try fixing the outlet temperature if else pass # NOTE: if passed then there would probably be a degree of freedom try: self.properties_out[:].pressure.\ fix(state_args_mixed["pressure"]) except AttributeError: init_log.warning("Trying to fix outlet pressure " "during initialization but pressure attribute " "unavailable in the state block. Initialization " "proceeding with a potential degree of freedom.") with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = solver.solve(self, tee=slc.tee) init_log.info("Mass balance solve {}.".format(idaeslog.condition(res))) # Activate energy balance self.enthalpy_mixing_equations.activate() try: self.properties_out[:].temperature.unfix() except AttributeError: pass with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = solver.solve(self, tee=slc.tee) init_log.info("Mass and energy balance solve {}.".format( idaeslog.condition(res))) # Activate pressure balance self.pressure_drop_equation.activate() try: self.properties_out[:].pressure.unfix() except AttributeError: pass if degrees_of_freedom(self) == 0: with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = solver.solve(self, tee=slc.tee) init_log.info("Mass, energy and pressure balance solve {}.".format( idaeslog.condition(res))) else: raise Exception("State vars fixed but degrees of freedom " "for tray block is not zero during " "initialization.") self.properties_in_liq.release_state(flags=liq_in_flags, outlvl=outlvl) self.properties_in_vap.release_state(flags=vap_in_flags, outlvl=outlvl) init_log.info("Initialization complete, status {}.".format( idaeslog.condition(res))) if self.config.is_feed_tray: return feed_flags
def initialize( self, state_args={}, outlvl=idaeslog.NOTSET, solver="ipopt", optarg={ "tol": 1e-6, "max_iter": 30 }, ): """ Initialize the inlet turbine stage model. This deactivates the specialized constraints, then does the isentropic turbine initialization, then reactivates the constraints and solves. Args: state_args (dict): Initial state for property initialization outlvl (int): Amount of output (0 to 3) 0 is lowest solver (str): Solver to use for initialization optarg (dict): Solver arguments dictionary """ init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit") solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit") # sp is what to save to make sure state after init is same as the start # saves value, fixed, and active state, doesn't load originally free # values, this makes sure original problem spec is same but initializes # the values of free vars sp = StoreSpec.value_isfixed_isactive(only_fixed=True) istate = to_json(self, return_dict=True, wts=sp) # Deactivate special constraints self.inlet_flow_constraint.deactivate() self.isentropic_enthalpy.deactivate() self.efficiency_correlation.deactivate() self.deltaP.unfix() self.ratioP.unfix() # Fix turbine parameters + eff_isen self.eff_nozzle.fix() self.blade_reaction.fix() self.flow_coeff.fix() self.blade_velocity.fix() # fix inlet and free outlet for t in self.flowsheet().config.time: for k, v in self.inlet.vars.items(): v[t].fix() for k, v in self.outlet.vars.items(): v[t].unfix() # If there isn't a good guess for efficeny or outlet pressure # provide something reasonable. eff = self.efficiency_isentropic[t] eff.fix( eff.value if value(eff) > 0.3 and value(eff) < 1.0 else 0.8) # for outlet pressure try outlet pressure, pressure ratio, delta P, # then if none of those look reasonable use a pressure ratio of 0.8 # to calculate outlet pressure Pout = self.outlet.pressure[t] Pin = self.inlet.pressure[t] prdp = value((self.deltaP[t] - Pin) / Pin) if value(Pout / Pin) > 0.98 or value(Pout / Pin) < 0.3: if value(self.ratioP[t]) < 0.98 and value( self.ratioP[t]) > 0.3: Pout.fix(value(Pin * self.ratioP)) elif prdp < 0.98 and prdp > 0.3: Pout.fix(value(prdp * Pin)) else: Pout.fix(value(Pin * 0.8)) else: Pout.fix() self.deltaP[:] = value(Pout - Pin) self.ratioP[:] = value(Pout / Pin) for t in self.flowsheet().config.time: self.properties_isentropic[t].pressure.value = value( self.outlet.pressure[t]) self.properties_isentropic[t].flow_mol.value = value( self.inlet.flow_mol[t]) self.properties_isentropic[t].enth_mol.value = value( self.inlet.enth_mol[t] * 0.95) self.outlet.flow_mol[t].value = value(self.inlet.flow_mol[t]) self.outlet.enth_mol[t].value = value(self.inlet.enth_mol[t] * 0.95) # Make sure the initialization problem has no degrees of freedom # This shouldn't happen here unless there is a bug in this dof = degrees_of_freedom(self) try: assert dof == 0 except: init_log.exception("degrees_of_freedom = {}".format(dof)) raise # one bad thing about reusing this is that the log messages aren't # really compatible with being nested inside another initialization super().initialize(state_args=state_args, outlvl=outlvl, solver=solver, optarg=optarg) # Free eff_isen and activate sepcial constarints self.efficiency_isentropic.unfix() self.outlet.pressure.unfix() self.inlet_flow_constraint.activate() self.isentropic_enthalpy.activate() self.efficiency_correlation.activate() slvr = SolverFactory(solver) slvr.options = optarg with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = slvr.solve(self, tee=slc.tee) init_log.info("Initialization Complete: {}".format( idaeslog.condition(res))) # reload original spec from_json(self, sd=istate, wts=sp)
def test_dof(self, model): assert degrees_of_freedom(model) == 0
def test_setInputs_reaction_block(rxn_prop): assert degrees_of_freedom(rxn_prop.fs.unit) == 0
def test_steadystate_power_plant_build(): # constructing and initializing dynamic power plant # not solving due to simulation time >20 min m = subcrit_plant.get_model(dynamic=False, init=False) assert m.dynamic is False assert degrees_of_freedom(m) == -5
def test_k_rxn(self): m = self._make_model() rxn_block = m.fs.reaction_block n_scen = 4 mols = pyunits.mol / pyunits.s kgs = pyunits.kg / pyunits.s K = pyunits.K bar = pyunits.bar state_values = { "gas_state.flow_mol": [1.0 * mols] * n_scen, "solid_state.flow_mass": [1.0 * kgs] * n_scen, "gas_state.temperature": [1000.0 * K, 1100.0 * K, 1200.0 * K, 1300.0 * K], "solid_state.temperature": [1000.0 * K, 1100.0 * K, 1200.0 * K, 1300.0 * K], "gas_state.pressure": [1.0 * bar] * n_scen, "solid_state.particle_porosity": [0.27] * n_scen, "gas_state.mole_frac_comp[O2]": [0.25] * n_scen, "gas_state.mole_frac_comp[N2]": [0.25] * n_scen, "gas_state.mole_frac_comp[H2O]": [0.25] * n_scen, "gas_state.mole_frac_comp[CO2]": [0.25] * n_scen, "solid_state.mass_frac_comp[Fe2O3]": [1.0 / 3.0] * n_scen, "solid_state.mass_frac_comp[Fe3O4]": [1.0 / 3.0] * n_scen, "solid_state.mass_frac_comp[Al2O3]": [1.0 / 3.0] * n_scen, } state_values = ComponentMap((m.fs.find_component(name), values) for name, values in state_values.items()) # Units of k_rxn are "non-physical" si units, chosen to be # consistent with the reaction rate rule. target_values = { "reaction_block.k_rxn[R1]": [ 5.7556e-5, 6.7076e-5, 7.6203e-5, 8.4888e-5, ], } target_values = ComponentMap((m.fs.find_component(name), values) for name, values in target_values.items()) assert degrees_of_freedom(m.fs) == 0 param_sweeper = ParamSweeper(n_scen, state_values, output_values=target_values) with param_sweeper: for inputs, outputs in param_sweeper: solve_strongly_connected_components(m.fs) # Make sure property equalites have been converged assert number_large_residuals(m.fs, tol=1e-8) == 0 # Sanity checks that inputs are properly set for var, val in inputs.items(): val = value(pyunits.convert(val, var.get_units())) assert var.value == pytest.approx(value(val), rel=1e-3) # Make sure properties have been calculated as expected for var, val in outputs.items(): val = value(pyunits.convert(val, var.get_units())) assert var.value == pytest.approx(value(val), rel=1e-3)
def test_dynamic_steam_cycle(): # constructing and initializing dynamic steam cycle flowsheet m = steam_cycle.get_model(dynamic=True) assert m.dynamic is True assert degrees_of_freedom(m) == 7
def test_reaction_rate(self): m = self._make_model() rxn_block = m.fs.reaction_block n_scen = 9 mols = pyunits.mol / pyunits.s kgs = pyunits.kg / pyunits.s K = pyunits.K bar = pyunits.bar state_values = { "gas_state.flow_mol": [1.0 * mols] * n_scen, "solid_state.flow_mass": [1.0 * kgs] * n_scen, "gas_state.temperature": [1273.0 * K] * n_scen, "solid_state.temperature": [ 1273.0 * K, 1273.0 * K, 1273.0 * K, 1273.0 * K, 1273.0 * K, 1273.0 * K, 1100.0 * K, 1200.0 * K, 1300.0 * K, ], "gas_state.pressure": [1.0 * bar] * n_scen, "solid_state.particle_porosity": [0.27] * n_scen, "gas_state.mole_frac_comp[O2]": [ 1.0, 0.7, 0.0, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, ], "gas_state.mole_frac_comp[N2]": [ 0.0, 0.1, 1 / 3, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, ], "gas_state.mole_frac_comp[H2O]": [ 0.0, 0.1, 1 / 3, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, ], "gas_state.mole_frac_comp[CO2]": [ 0.0, 0.1, 1 / 3, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, ], "solid_state.mass_frac_comp[Fe2O3]": [ 1 / 3, 1 / 3, 1 / 3, 2 / 3, 0.0, 1 / 3, 1 / 3, 1 / 3, 1 / 3, ], "solid_state.mass_frac_comp[Fe3O4]": [ 1 / 3, 1 / 3, 1 / 3, 0.0, 2 / 3, 1 / 3, 1 / 3, 1 / 3, 1 / 3, ], "solid_state.mass_frac_comp[Al2O3]": [1 / 3] * n_scen, } state_values = ComponentMap((m.fs.find_component(name), values) for name, values in state_values.items()) molm3s = pyunits.mol / pyunits.m**3 / pyunits.s target_values = { "reaction_block.reaction_rate[R1]": [ 351.367 * molm3s, 245.957 * molm3s, 0.0 * molm3s, 0.0 * molm3s, 271.731 * molm3s, 87.842 * molm3s, 71.344 * molm3s, 81.051 * molm3s, 90.288 * molm3s, ], } target_values = ComponentMap((m.fs.find_component(name), values) for name, values in target_values.items()) assert degrees_of_freedom(m.fs) == 0 param_sweeper = ParamSweeper(n_scen, state_values, output_values=target_values) with param_sweeper: for inputs, outputs in param_sweeper: solve_strongly_connected_components(m.fs) # Make sure property equalites have been converged assert number_large_residuals(m.fs, tol=1e-8) == 0 # Sanity checks that inputs are properly set for var, val in inputs.items(): val = value(pyunits.convert(val, var.get_units())) assert var.value == pytest.approx(value(val), abs=1e-3) # Make sure properties have been calculated as expected for var, val in outputs.items(): if value(val) != 0: # To get around Pyomo issue #1627 val = value(pyunits.convert(val, var.get_units())) assert var.value == pytest.approx(value(val), abs=1e-3)
def test_subcritical_recyrculation_system(): m = recyrc.main() assert degrees_of_freedom(m) == 0
def initialize(self, state_args=None, solver=None, optarg=None, outlvl=idaeslog.NOTSET): init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit") solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit") if solver is None: init_log.warning("Solver not provided. Default solver(ipopt) " " being used for initialization.") solver = get_default_solver() # Initialize the inlet and outlet state blocks. Calling the state # blocks initialize methods directly so that custom set of state args # can be passed to the inlet and outlet state blocks as control_volume # initialize method initializes the state blocks with the same # state conditions. flags = self.control_volume.properties_in. \ initialize(state_args=state_args, solver=solver, optarg=optarg, outlvl=outlvl, hold_state=True) # Initialize outlet state block at same conditions of inlet except # the temperature. Set the temperature to a temperature guess based # on the desired boilup_ratio. # Get index for bubble point temperature and and assume it # will have only a single phase equilibrium pair. This is to # support the generic property framework where the T_bubble # is indexed by the phases_in_equilibrium. In distillation, # the assumption is that there will only be a single pair # i.e. vap-liq. idx = next( iter(self.control_volume.properties_in[0].temperature_bubble)) temp_guess = 0.5 * ( value(self.control_volume.properties_in[0].temperature_dew[idx]) - value(self.control_volume.properties_in[0]. temperature_bubble[idx])) + \ value(self.control_volume.properties_in[0].temperature_bubble[idx]) state_args_outlet = {} state_dict_outlet = (self.control_volume.properties_in[ self.flowsheet().config.time.first()].define_port_members()) for k in state_dict_outlet.keys(): if state_dict_outlet[k].is_indexed(): state_args_outlet[k] = {} for m in state_dict_outlet[k].keys(): state_args_outlet[k][m] = value(state_dict_outlet[k][m]) else: if k != "temperature": state_args_outlet[k] = value(state_dict_outlet[k]) else: state_args_outlet[k] = temp_guess self.control_volume.properties_out.initialize( state_args=state_args_outlet, solver=solver, optarg=optarg, outlvl=outlvl, hold_state=False) if degrees_of_freedom(self) == 0: 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: raise ConfigurationError( "State vars fixed but degrees of freedom " "for reboiler is not zero during " "initialization. Please ensure that the boilup_ratio " "or the outlet temperature is fixed.") self.control_volume.properties_in.\ release_state(flags=flags, outlvl=outlvl)
def test_subcritical_boiler_dynamic_build(): m = blr.get_model(dynamic=True, init=False) assert degrees_of_freedom(m) == 223
def test_dof(self, iapws): assert degrees_of_freedom(iapws) == 0
def test_init(initialize_model): m, solver = initialize_model # check that the model solved properly and has 0 degrees of freedom assert (degrees_of_freedom(m) == 0) for c in activated_equalities_generator(m): assert (abs(c.body() - c.lower) < 1e-4)
def test_dof(self, btg): assert degrees_of_freedom(btg) == 0
def test_power_plan(): # import steam cycle and build concrete model m, solver = steam_cycle.main() print(degrees_of_freedom(m)) #at this point we have a flowsheet with "steam cycle" that solves # correctly, with 0 degrees of freedom. # next step is to import and build the boiler heat exchanger network # importing the boiler heat exchanger network from (boiler_subflowsheet_build.py) # will basically append all the unit models into our model ("m") # model "m" has been created a few lines above # import the models (ECON, WW, PrSH, PlSH, FSH, Spliter, Mixer, Reheater) # see boiler_subflowhseet_build.py for a beter description blr.build_boiler(m.fs) #initialize boiler network models (one by one) blr.initialize(m) # at this point we have both flowsheets (steam cycle + boiler network) # in the same model/concrete object ("m") # however they are disconnected. Here we want to solve them at the same time # this is a square problem (i.e. degrees of freedom = 0) # print('solving square problem disconnected') results = solver.solve(m, tee=True) # at this point we want to connect the units in both flowsheets # Economizer inlet = Feed water heater 8 outlet (water) # HP inlet = Attemperator outlet (steam) # Reheater inlet (steam) = HP split 7 outlet (last stage of HP turbine) # IP inlet = Reheater outlet steam7 blr.unfix_inlets(m) # print(degrees_of_freedom(m)) # MS.to_json(m, fname = 'SCPC_full.json') # MS.from_json(m, fname = 'SCPC_full.json') # deactivate constraints linking the FWH8 to HP turbine m.fs.boiler_pressure_drop.deactivate() m.fs.close_flow.deactivate() m.fs.turb.constraint_reheat_flow.deactivate() m.fs.turb.constraint_reheat_press.deactivate() m.fs.turb.constraint_reheat_temp.deactivate() m.fs.turb.inlet_split.inlet.enth_mol.unfix() m.fs.turb.inlet_split.inlet.pressure.unfix() # user can fix the boiler feed water pump pressure (uncomenting next line) # m.fs.bfp.outlet.pressure[:].fix(26922222.222)) m.fs.FHWtoECON = Arc(source=m.fs.fwh8.desuperheat.outlet_2, destination=m.fs.ECON.side_1_inlet) m.fs.Att2HP = Arc(source=m.fs.ATMP1.outlet, destination=m.fs.turb.inlet_split.inlet) m.fs.HPout2RH = Arc(source=m.fs.turb.hp_split[7].outlet_1, destination=m.fs.RH.side_1_inlet) m.fs.RHtoIP = Arc(source=m.fs.RH.side_1_outlet, destination=m.fs.turb.ip_stages[1].inlet) pyo.TransformationFactory("network.expand_arcs").apply_to(m) #unfix boiler connections m.fs.ECON.side_1_inlet.flow_mol.unfix() m.fs.ECON.side_1_inlet.enth_mol[0].unfix() m.fs.ECON.side_1_inlet.pressure[0].unfix() m.fs.RH.side_1_inlet.flow_mol.unfix() m.fs.RH.side_1_inlet.enth_mol[0].unfix() m.fs.RH.side_1_inlet.pressure[0].unfix() m.fs.hotwell.makeup.flow_mol[:].setlb(-1.0) # if user has trouble with infeasible solutions, an easy test # is to deactivate the link to HP turbine (m.fs.Att2HP_expanded "enth_mol and pressure" equalities) # and fix inlet pressure and enth_mol to turbine (m.fs.turb.inlet_split.inlet) # (then double check the values from m.fs.ATMP1.outlet) # m.fs.Att2HP_expanded.enth_mol_equality.deactivate() # m.fs.Att2HP_expanded.pressure_equality.deactivate() m.fs.turb.inlet_split.inlet.pressure.fix(2.423e7) # m.fs.turb.inlet_split.inlet.enth_mol.fix(62710.01) # finally, since we want to maintain High Pressure (HP) inlet temperature constant (~866 K) # we need to fix Attemperator enthalpy outlet and unfix heat duty to Platen superheater # note fixing enthalpy to control temperature is only valid because pressure is also fixed m.fs.ATMP1.outlet.enth_mol[0].fix(62710.01) m.fs.PlSH.heat_duty[:].unfix() #fix(5.5e7) # m.fs.ATMP1.SprayWater.flow_mol[0].unfix() # print(degrees_of_freedom(m)) solver.options = { "tol": 1e-6, "linear_solver": "ma27", "max_iter": 40, } #square problems tend to work better without bounds strip_bounds = pyo.TransformationFactory('contrib.strip_var_bounds') strip_bounds.apply_to(m, reversible=True) # this is the final solve with both flowsheets connected results = solver.solve(m, tee=True) strip_bounds.revert(m)
def test_degrees_of_freedom(self, model): assert degrees_of_freedom(model.fs.unit) == 0
def th( delta_temperature_callback=delta_temperature_underwood_tune_callback, tout_1=809.55, tout_2=788.53, ): m = ConcreteModel() m.fs = FlowsheetBlock(default={"dynamic": False}) m.fs.properties = PhysicalParameterTestBlock() m.fs.prop_steam = iapws95.Iapws95ParameterBlock() m.fs.prop_fluegas = FlueGasParameterBlock() m.fs.unit = BoilerHeatExchanger( default={ "delta_temperature_callback": delta_temperature_callback, "tube": { "property_package": m.fs.prop_steam }, "shell": { "property_package": m.fs.prop_fluegas }, "has_pressure_change": True, "has_holdup": False, "flow_pattern": HeatExchangerFlowPattern.countercurrent, "tube_arrangement": TubeArrangement.inLine, "side_1_water_phase": "Liq", "has_radiation": True, }) # Set inputs h = value(iapws95.htpx(773.15 * pyunits.K, 2.5449e7 * pyunits.Pa)) m.fs.unit.side_1_inlet.flow_mol[0].fix(24678.26) # mol/s m.fs.unit.side_1_inlet.enth_mol[0].fix(h) # J/mol m.fs.unit.side_1_inlet.pressure[0].fix(2.5449e7) # Pascals # FLUE GAS Inlet from Primary Superheater FGrate = 28.3876e3 * 0.18 # mol/s equivalent of ~1930.08 klb/hr # Use FG molar composition to set component flow rates (baseline report) m.fs.unit.side_2_inlet.flow_mol_comp[0, "H2O"].fix(FGrate * 8.69 / 100) m.fs.unit.side_2_inlet.flow_mol_comp[0, "CO2"].fix(FGrate * 14.49 / 100) m.fs.unit.side_2_inlet.flow_mol_comp[0, "N2"].fix(FGrate * 74.34 / 100) m.fs.unit.side_2_inlet.flow_mol_comp[0, "O2"].fix(FGrate * 2.47 / 100) m.fs.unit.side_2_inlet.flow_mol_comp[0, "NO"].fix(FGrate * 0.0006) m.fs.unit.side_2_inlet.flow_mol_comp[0, "SO2"].fix(FGrate * 0.002) m.fs.unit.side_2_inlet.temperature[0].fix(1102.335) m.fs.unit.side_2_inlet.pressure[0].fix(100145) # Primary Superheater ITM = 0.0254 # inch to meter conversion m.fs.unit.tube_di.fix((2.5 - 2 * 0.165) * ITM) m.fs.unit.tube_thickness.fix(0.165 * ITM) m.fs.unit.pitch_x.fix(3 * ITM) # gas path transverse width 54.78 ft / number of columns m.fs.unit.pitch_y.fix(54.78 / 108 * 12 * ITM) m.fs.unit.tube_length.fix(53.13 * 12 * ITM) m.fs.unit.tube_nrow.fix(20 * 2) m.fs.unit.tube_ncol.fix(108) m.fs.unit.nrow_inlet.fix(4) m.fs.unit.delta_elevation.fix(50) m.fs.unit.tube_r_fouling = 0.000176 # (0.001 h-ft^2-F/BTU) m.fs.unit.tube_r_fouling = 0.003131 # (0.03131 - 0.1779 h-ft^2-F/BTU) if m.fs.unit.config.has_radiation is True: m.fs.unit.emissivity_wall.fix(0.7) # wall emissivity # correction factor for overall heat transfer coefficient m.fs.unit.fcorrection_htc.fix(1.5) # correction factor for pressure drop calc tube side m.fs.unit.fcorrection_dp_tube.fix(1.0) # correction factor for pressure drop calc shell side m.fs.unit.fcorrection_dp_shell.fix(1.0) assert degrees_of_freedom(m) == 0 iscale.calculate_scaling_factors(m) m.fs.unit.initialize() results = solver.solve(m) # Check for optimal solution assert check_optimal_termination(results) assert value( m.fs.unit.side_1.properties_out[0].temperature) == pytest.approx( tout_1, abs=0.5) assert value( m.fs.unit.side_2.properties_out[0].temperature) == pytest.approx( tout_2, abs=0.5)
def initialize(blk, state_args=None, hold_state=False, state_vars_fixed=False, outlvl=idaeslog.NOTSET, solver="ipopt", optarg={"tol": 1e-8}): """ Initialization routine for property package. Keyword Arguments: state_args : Dictionary with initial guesses for the state vars chosen. Note that if this method is triggered through the control volume, and if initial guesses were not provided at the unit model level, the control volume passes the inlet values as initial guess. Keys for the state_args dictionary are: flow_mass, temperature, and mass_frac_comp outlvl : sets output level of initialization routine optarg : solver options dictionary object (default=None) solver : str indicating whcih solver to use during initialization (default = "ipopt") hold_state : flag indicating whether the initialization routine should unfix any state variables fixed during initialization (default=False). - True - states varaibles are not unfixed, and a dict of returned containing flags for which states were fixed during initialization. - False - state variables are unfixed after initialization by calling the relase_state method Returns: If hold_states is True, returns a dict containing flags for which states were fixed during initialization. """ init_log = idaeslog.getInitLogger(blk.name, outlvl, tag="properties") solve_log = idaeslog.getSolveLogger(blk.name, outlvl, tag="properties") init_log.info_high('Starting initialization') # Deactivate the constraints specific for outlet block i.e. # when defined state is False for k in blk.keys(): if blk[k].config.defined_state is False: blk[k].sum_component_eqn.deactivate() # Fix state variables if not already fixed if state_vars_fixed is False: flags = fix_state_vars(blk, state_args) else: # Check when the state vars are fixed already result in dof 0 for k in blk.keys(): if degrees_of_freedom(blk[k]) != 0: raise Exception("State vars fixed but degrees of freedom " "for state block is not zero during " "initialization.") # --------------------------------------------------------------------- # Initialise values for k in blk.keys(): if hasattr(blk[k], "density_skeletal_constraint"): calculate_variable_from_constraint( blk[k].dens_mass_skeletal, blk[k].density_skeletal_constraint) if hasattr(blk[k], "mixture_heat_capacity_eqn"): calculate_variable_from_constraint( blk[k].cp_mass, blk[k].mixture_heat_capacity_eqn) if hasattr(blk[k], "mixture_enthalpy_eqn"): calculate_variable_from_constraint(blk[k].enth_mass, blk[k].mixture_enthalpy_eqn) for j in blk[k]._params.component_list: if hasattr(blk[k], "cp_shomate_eqn"): calculate_variable_from_constraint( blk[k].cp_mol_comp[j], blk[k].cp_shomate_eqn[j]) if hasattr(blk[k], "enthalpy_shomate_eqn"): calculate_variable_from_constraint( blk[k].enth_mol_comp[j], blk[k].enthalpy_shomate_eqn[j]) # Solve property block if non-empty free_vars = 0 for k in blk.keys(): free_vars += number_unfixed_variables_in_activated_equalities( blk[k]) if free_vars > 0: # Create solver opt = get_solver(solver, optarg) with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc: res = solve_indexed_blocks(opt, [blk], tee=slc.tee) else: res = "" init_log.info_high("Initialization complete {}.".format( idaeslog.condition(res))) # --------------------------------------------------------------------- if state_vars_fixed is False: if hold_state is True: return flags else: blk.release_state(flags)
def _test_add_noise_at_time(): mod = make_model(horizon=2, ntfe=20) time = mod.fs.time t0 = time.first() assert degrees_of_freedom(mod) == 0 scalar_vars, dae_vars = flatten_dae_components(mod.fs, time, Var) diff_vars = [ Reference(mod.fs.cstr.control_volume.energy_holdup[:, 'aq']), Reference(mod.fs.cstr.control_volume.material_holdup[:, 'aq', 'S']), Reference(mod.fs.cstr.control_volume.material_holdup[:, 'aq', 'E']), Reference(mod.fs.cstr.control_volume.material_holdup[:, 'aq', 'C']), Reference(mod.fs.cstr.control_volume.material_holdup[:, 'aq', 'P']) ] for t in time: diff_vars[0][t].setlb(290) diff_vars[0][t].setub(310) for i in range(1, 5): diff_vars[i][t].setlb(0) diff_vars[i][t].setub(1) # Pretend this is mole fraction... assert diff_vars[0][0].value == 300 for i in range(1, 5): assert diff_vars[i][0].value == 0.001 copy_values_at_time(diff_vars, diff_vars, [t for t in time if t != t0], t0) for seed in [4, 8, 15, 16, 23, 42]: random.seed(seed) weights = [10, 0.001, 0.001, 0.001, 0.001] nom_vals = add_noise_at_time(diff_vars, 0, weights=weights) assert nom_vals[0][0] == 300 assert diff_vars[0][0].value != 300 assert diff_vars[0][0].value == approx(300, abs=2) for i in range(1, 5): assert nom_vals[0][i] == 0.001 # ^ nom_vals indexed by time, then var-index. This is confusing, # might need to change (or only accept one time point at a time) assert diff_vars[i][0].value != 0.001 assert diff_vars[i][0].value == approx(0.001, abs=2e-4) # Within four standard deviations should be a safe check for i in range(0, 5): diff_vars[i][0].set_value(nom_vals[0][i]) # Reset and try again with new seed # Try providing function for uniform random rand_fcn = random.uniform random_arg_dict = { 'range_list': [(295, 305), (0.001, 0.01), (0.001, 0.01), (0.001, 0.01), (0.001, 0.01)] } def args_fcn(i, val, **kwargs): # args_fcn expects arguments like this range_list = kwargs.pop('range_list', None) return range_list[i] nom_vals = add_noise_at_time(diff_vars, 0.5, random_function=rand_fcn, args_function=args_fcn, random_arg_dict=random_arg_dict) assert nom_vals[0.5][0] == 300 assert diff_vars[0][0.5].value != 300 assert 295 <= diff_vars[0][0.5].value <= 305 for i in range(1, 5): assert nom_vals[0.5][i] == 0.001 assert diff_vars[i][0.5].value != 0.001 assert 0.001 <= diff_vars[i][0.5].value <= 0.01 # Try to get some bound violations random_arg_dict = { 'range_list': [(295, 305), (1, 2), (1, 2), (1, 2), (1, 2)] } nom_vals = add_noise_at_time(diff_vars, 1, random_function=rand_fcn, args_function=args_fcn, random_arg_dict=random_arg_dict, bound_strategy='push', bound_push=0.01) for i in range(1, 5): assert diff_vars[i][1].value == 0.99 random.seed(123) with pytest.raises(ValueError) as exc_test: # Large weights - one of these lower bounds should fail... nom_vals = add_noise_at_time(diff_vars, 1.5, bound_strategy='discard', discard_limit=0, weights=[1, 1, 1, 1, 1], sig_0=0.05)