def _extend_presumed_pdf_first_dim(lam_lib, pdf_spec, added_suffix, num_procs, verbose=False): turb_dims = [ Dimension(d.name + added_suffix, d.values) for d in lam_lib.dims ] if pdf_spec.pdf != 'delta': turb_dims.append( Dimension( pdf_spec.variance_name, pdf_spec.scaled_variance_values if pdf_spec.scaled_variance_values is not None else pdf_spec.variance_values)) turb_lib = Library(*turb_dims) for p in lam_lib.props: turb_lib[p] = turb_lib.get_empty_dataset() if pdf_spec.pdf == 'delta': turb_lib = Library(*turb_dims) for p in lam_lib.props: turb_lib[p] = lam_lib[p].copy() return turb_lib if verbose: cput0 = perf_counter() num_integrals = len(turb_lib.props) for d in turb_lib.dims: num_integrals *= d.npts print( f'{pdf_spec.variance_name}: computing {num_integrals} integrals... ', end='', flush=True) if num_procs == 1: for p in turb_lib.props: turb_lib[p] = _convolve_full_property(p, None, turb_lib, lam_lib, pdf_spec) else: pool = Pool(processes=num_procs) manager = Manager() managed_dict = manager.dict() pool.map( partial(_convolve_full_property, managed_dict=managed_dict, turb_lib=turb_lib, lam_lib=lam_lib, pdf_spec=pdf_spec), turb_lib.props) for p in managed_dict: turb_lib[p] = managed_dict[p] if verbose: cputf = perf_counter() dcput = cputf - cput0 avg = float(num_integrals) / dcput print( f'completed in {dcput:.1f} seconds, average = {int(avg)} integrals/s.' ) return turb_lib
def _apply_presumed_pdf_1var(library, variable_name, pdf_spec, added_suffix=_mean_suffix, num_procs=1, verbose=False): require_pytabprops('apply_presumed_PDF_model') index = 0 for d in library.dims: if d.name == variable_name: break else: index += 1 if index == len(library.dims): raise KeyError( f'Invalid variable name \"{variable_name}\" for library with names {library.dim_names}' ) _pdf_spec = copy.copy(pdf_spec) if _pdf_spec.variance_name is None: if variable_name == _mixture_fraction_name: _pdf_spec.variance_name = _scaled_scalar_variance_name else: _pdf_spec.variance_name = variable_name + '_variance' if index == 0: library_t = _extend_presumed_pdf_first_dim(library, _pdf_spec, added_suffix, num_procs, verbose) else: swapped_dims = library.dims swapped_dims[index], swapped_dims[0] = swapped_dims[0], swapped_dims[ index] swapped_lib = Library(*swapped_dims) for p in library.props: swapped_lib[p] = np.swapaxes(library[p], 0, index) swapped_lib_t = _extend_presumed_pdf_first_dim(swapped_lib, _pdf_spec, added_suffix, num_procs, verbose) swapped_lib_t_dims = copy.copy(swapped_lib_t.dims) swapped_lib_t_dims[0], swapped_lib_t_dims[index] = swapped_lib_t_dims[ index], swapped_lib_t_dims[0] library_t = Library(*swapped_lib_t_dims) for p in swapped_lib_t.props: library_t[p] = np.swapaxes(swapped_lib_t[p], index, 0) for key in library.extra_attributes: library_t.extra_attributes[key] = copy.copy( library.extra_attributes[key]) return library_t
def _build_nonadiabatic_defect_slfm_library(flamelet_specs, heat_loss_expansion='transient', diss_rate_values=np.logspace( -3, 2, 16), diss_rate_ref='stoichiometric', verbose=True, solver_verbose=False, h_stoich_spacing=10.e3, num_procs=1, integration_args=None, n_defect_st=32, extend_defect_dim=False): if isinstance(flamelet_specs, dict): flamelet_specs = FlameletSpec(**flamelet_specs) m = flamelet_specs.mech_spec fuel = flamelet_specs.fuel_stream oxy = flamelet_specs.oxy_stream cput00 = _write_library_header('nonadiabatic (defect) SLFM', m, fuel, oxy, verbose) ugt = _build_unstructured_nonadiabatic_defect_slfm_library( flamelet_specs, heat_loss_expansion, diss_rate_values, diss_rate_ref, verbose, solver_verbose, h_stoich_spacing, num_procs, integration_args) structured_defect_table, x_values, g_values = _interpolate_to_structured_defect_dimension( ugt, n_defect_st, verbose=verbose, extend=extend_defect_dim) key0 = list(structured_defect_table.keys())[0] z_values = structured_defect_table[key0][_mixture_fraction_name] z_dim = Dimension(_mixture_fraction_name, z_values) x_dim = Dimension(_dissipation_rate_name + _stoich_suffix, x_values) g_dim = Dimension(_enthalpy_defect_name + _stoich_suffix, g_values) output_library = Library(z_dim, x_dim, g_dim) output_library.extra_attributes['mech_spec'] = m for quantity in structured_defect_table[key0]: values = output_library.get_empty_dataset() for ix, x in enumerate(x_values): for ig, g in enumerate(g_values): values[:, ix, ig] = structured_defect_table[(x, g)][quantity] output_library[quantity] = values _write_library_footer(cput00, verbose) return output_library
def test(self): output_library = run() gold_file = abspath(join('tests', 'tabulation', 'adiabatic_slfm', 'gold.pkl')) gold_library = Library.load_from_file(gold_file) for prop in gold_library.props: self.assertIsNone(assert_allclose(gold_library[prop], output_library[prop], rtol=1.e-6, atol=1.e-6))
def test_parallel(self): output_library = run(num_procs=2) gold_file = abspath(join('tests', 'tabulation', 'nonadiabatic_defect_transient_slfm', 'gold.pkl')) gold_library = Library.load_from_file(gold_file) for prop in gold_library.props: self.assertIsNone(assert_allclose(gold_library[prop], output_library[prop], rtol=2.e-4, atol=1.e-4))
def test(self): output_library = run() gold_file = abspath( join('tests', 'tabulation', 'nonadiabatic_defect_burke_schumann', 'gold.pkl')) gold_library = Library.load_from_file(gold_file) for prop in gold_library.props: self.assertIsNone( assert_allclose(gold_library[prop], output_library[prop], atol=1.e-14))
def test(self): output_library = run() gold_file = abspath(join('tests', 'tabulation', 'turb_adiabatic_equilibrium', 'gold.pkl')) gold_library = Library.load_from_file(gold_file) for prop in gold_library.props: self.assertIsNone(assert_allclose(gold_library[prop], output_library[prop], atol=1.e-14)) self.assertEqual(output_library.dims[0].name, 'mixture_fraction_mean') self.assertEqual(output_library.dims[1].name, 'scaled_scalar_variance_mean')
def test(self): gas = ct.Solution('h2o2.yaml', transport_model='Multi') mech = ChemicalMechanismSpec.from_solution(gas) fs = FlameletSpec(mech_spec=mech, initial_condition='equilibrium', oxy_stream=mech.stream('TPX', (300, 1.e5, 'O2:1, N2:3.76')), fuel_stream=mech.stream('TPY', (300, 1.e5, 'H2:1')), grid_points=16) eq_lib1 = build_adiabatic_eq_library(fs, verbose=False) z_dim = Dimension(eq_lib1.mixture_fraction_name, eq_lib1.mixture_fraction_values) fuel_T_dim = Dimension('fuel_temperature', np.linspace(0.0, 1.0, 4)) air_T_dim = Dimension('air_temperature', np.linspace(0.0, 1.0, 3)) eq_lib2 = Library(z_dim, fuel_T_dim) eq_lib2T = Library(fuel_T_dim, z_dim) eq_lib3 = Library(z_dim, fuel_T_dim, air_T_dim) eq_lib3T1 = Library(fuel_T_dim, z_dim, air_T_dim) eq_lib3T2 = Library(fuel_T_dim, air_T_dim, z_dim) for p in eq_lib1.props: eq_lib2[p] = eq_lib2.get_empty_dataset() eq_lib2T[p] = eq_lib2T.get_empty_dataset() eq_lib3[p] = eq_lib3.get_empty_dataset() eq_lib3T1[p] = eq_lib3T1.get_empty_dataset() eq_lib3T2[p] = eq_lib3T2.get_empty_dataset() for i, fuel_T_offset in enumerate(fuel_T_dim.values): fuel_T = 300 + fuel_T_offset * 500. fs2 = copy(fs) fs2.fuel_stream.TP = fuel_T, 1.e5 eq_tmp = build_adiabatic_eq_library(fs2, verbose=False) for p in eq_lib1.props: eq_lib2[p][:, i] = eq_tmp[p] eq_lib2T[p][i, :] = eq_tmp[p] for j, air_T_offset in enumerate(air_T_dim.values): air_T = 300 + air_T_offset * 500. fs3 = copy(fs2) fs3.oxy_stream.TP = air_T, 1.e5 eq_tmp = build_adiabatic_eq_library(fs3, verbose=False) for p in eq_lib1.props: eq_lib3[p][:, i, j] = eq_tmp[p] eq_lib3T1[p][i, :, j] = eq_tmp[p] eq_lib3T2[p][i, j, :] = eq_tmp[p] nonT_props = list(eq_lib1.props) nonT_props.remove('temperature') eq_lib1.remove(*nonT_props) eq_lib2.remove(*nonT_props) eq_lib2T.remove(*nonT_props) eq_lib3.remove(*nonT_props) eq_lib3T1.remove(*nonT_props) eq_lib3T2.remove(*nonT_props) z_svv = np.linspace(0., 1., 6) Tf_svv = np.linspace(0., 1., 5) eq_lib1_t = apply_presumed_PDF_model(eq_lib1, 'ClipGauss', z_svv, verbose=False) eq_lib2_t = apply_presumed_PDF_model(eq_lib2, 'ClipGauss', z_svv, verbose=False) eq_lib3_t = apply_presumed_PDF_model(eq_lib3, 'ClipGauss', z_svv, num_procs=1, verbose=False) eq_lib2T_t = apply_presumed_PDF_model(eq_lib2T, 'ClipGauss', z_svv, verbose=False) eq_lib3T1_t = apply_presumed_PDF_model(eq_lib3T1, 'ClipGauss', z_svv, num_procs=1, verbose=False) eq_lib3T2_t = apply_presumed_PDF_model(eq_lib3T2, 'ClipGauss', z_svv, num_procs=1, verbose=False) eq_lib2_tt = apply_presumed_PDF_model(eq_lib2_t, 'Beta', Tf_svv, 'fuel_temperature_mean', '', 'Tfvar', num_procs=1, verbose=False) eq_lib3_tt = apply_presumed_PDF_model(eq_lib3_t, 'Beta', Tf_svv, 'fuel_temperature_mean', '', 'Tfvar', num_procs=1, verbose=False) def get_dim_names(lib): return [d.name for d in lib.dims] self.assertEqual(['mixture_fraction'], get_dim_names(eq_lib1)) self.assertEqual(['mixture_fraction_mean', 'scaled_scalar_variance_mean'], get_dim_names(eq_lib1_t)) self.assertEqual(['mixture_fraction', 'fuel_temperature'], get_dim_names(eq_lib2)) self.assertEqual(['mixture_fraction_mean', 'fuel_temperature_mean', 'scaled_scalar_variance_mean'], get_dim_names(eq_lib2_t)) self.assertEqual( ['mixture_fraction_mean', 'fuel_temperature_mean', 'scaled_scalar_variance_mean', 'Tfvar'], get_dim_names(eq_lib2_tt)) self.assertEqual(['mixture_fraction', 'fuel_temperature', 'air_temperature'], get_dim_names(eq_lib3)) self.assertEqual(['mixture_fraction_mean', 'fuel_temperature_mean', 'air_temperature_mean', 'scaled_scalar_variance_mean'], get_dim_names(eq_lib3_t)) self.assertEqual(['mixture_fraction_mean', 'fuel_temperature_mean', 'air_temperature_mean', 'scaled_scalar_variance_mean', 'Tfvar'], get_dim_names(eq_lib3_tt)) self.assertEqual(['fuel_temperature', 'mixture_fraction'], get_dim_names(eq_lib2T)) self.assertEqual(['fuel_temperature_mean', 'mixture_fraction_mean', 'scaled_scalar_variance_mean'], get_dim_names(eq_lib2T_t), eq_lib2T_t) self.assertEqual(['fuel_temperature', 'mixture_fraction', 'air_temperature'], get_dim_names(eq_lib3T1)) self.assertEqual(['fuel_temperature', 'air_temperature', 'mixture_fraction'], get_dim_names(eq_lib3T2)) self.assertEqual(['fuel_temperature_mean', 'mixture_fraction_mean', 'air_temperature_mean', 'scaled_scalar_variance_mean'], get_dim_names(eq_lib3T1_t)) self.assertEqual(['fuel_temperature_mean', 'air_temperature_mean', 'mixture_fraction_mean', 'scaled_scalar_variance_mean'], get_dim_names(eq_lib3T2_t)) self.assertFalse(np.any(np.isnan(eq_lib1['temperature']))) self.assertFalse(np.any(np.isnan(eq_lib1_t['temperature']))) self.assertFalse(np.any(np.isnan(eq_lib2['temperature']))) self.assertFalse(np.any(np.isnan(eq_lib2T['temperature']))) self.assertFalse(np.any(np.isnan(eq_lib2_t['temperature']))) self.assertFalse(np.any(np.isnan(eq_lib2T_t['temperature']))) self.assertFalse(np.any(np.isnan(eq_lib3['temperature']))) self.assertFalse(np.any(np.isnan(eq_lib3T1['temperature']))) self.assertFalse(np.any(np.isnan(eq_lib3T2['temperature']))) self.assertFalse(np.any(np.isnan(eq_lib3_t['temperature']))) self.assertFalse(np.any(np.isnan(eq_lib3_tt['temperature']))) self.assertFalse(np.any(np.isnan(eq_lib3T1_t['temperature']))) self.assertFalse(np.any(np.isnan(eq_lib3T2_t['temperature']))) self.assertIsNone(assert_allclose(eq_lib2T['temperature'].T, eq_lib2['temperature'])) self.assertIsNone(assert_allclose(np.swapaxes(eq_lib3T1['temperature'], 0, 1), eq_lib3['temperature'])) self.assertIsNone(assert_allclose(np.swapaxes(np.swapaxes(eq_lib3T2['temperature'], 1, 2), 0, 1), eq_lib3['temperature'])) self.assertIsNone(assert_allclose(np.squeeze(eq_lib1_t['temperature'][:, 0]), eq_lib1['temperature'])) self.assertIsNone(assert_allclose(np.squeeze(eq_lib2_t['temperature'][:, :, 0]), eq_lib2['temperature'])) self.assertIsNone(assert_allclose(np.squeeze(eq_lib3_t['temperature'][:, :, :, 0]), eq_lib3['temperature'])) self.assertIsNone(assert_allclose(np.squeeze(eq_lib3_tt['temperature'][:, :, :, 0, 0]), eq_lib3['temperature']))
def integrate(self, stop_at_time=None, stop_at_steady=None, stop_criteria=None, first_time_step=1.e-6, max_time_step=1.e6, minimum_time_step_count=40, transient_tolerance=1.e-10, write_log=False, log_rate=100, maximum_steps_per_jacobian=1, nonlinear_solve_tolerance=1.e-12, linear_solver='lapack', plot=None, stepper_type=KennedyCarpenterS6P4Q3, nlsolver_type=SimpleNewtonSolver, stepcontrol_type=PIController, extra_integrator_args=dict(), extra_stepper_args=dict(), extra_nlsolver_args=dict(), extra_stepcontrol_args=dict(), save_first_and_last_only=False): """Base method for reactor integration Parameters ---------- stop_at_time : float The final time to stop the simulation at stop_at_steady : float The tolerance at which a steady state is decided upon and stopped at stop_criteria : callable (t, state, residual, n_steps) Any callable that returns True when the simulation should stop first_time_step : float The time step size initially used by the time integrator max_time_step : float The largest time step the time stepper is allowed to take minimum_time_step_count : int The minimum number of time steps to run (helpful for slowly evolving simulations, for instance those with low starting temperatures) transient_tolerance : float the target temporal error for transient integration write_log : bool whether or not to print integration statistics and status during the simulation log_rate : int how often to print log information maximum_steps_per_jacobian : int maximum number of steps Spitfire allows before the Jacobian must be re-evaluated - keep low for robustness, try to increase for performance on large mechanisms nonlinear_solve_tolerance : float tolerance for the nonlinear solver used in implicit time stepping (optional, default: 1e-12) linear_solver : str which linear solver to use, at the moment either 'lapack' (dense, direct) or 'superlu' (sparse, direct) are available plot : list List of variables (temperature and/or specific species names) to be plotted after the time integration completes. No plot is shown if a list is not provided. Temperature is plotted in the first subplot if any list of variables is provided for plotting (even if temperature is not specified in the list of variables). Species mass fractions will be plotted in a second subplot if any species names are provided in the list of variables. stepper_type : spitfire.time.TimeStepper which (single step) stepper method to use (optional, default: ESDIRK64) nlsolver_type : spitfire.time.NonlinearSolver which nonlinear solver method to use (optional, default: SimpleNewtonSolver) stepcontrol_type : spitfire.time.StepControl which time step adaptation method to use (optional, default: PIController) extra_integrator_args : dict any extra arguments to specify to the time integrator - arguments passed to the odesolve method extra_stepper_args : dict extra arguments to specify on the spitfire.time.TimeStepper object extra_nlsolver_args : dict extra arguments to specify on the spitfire.time.NonlinearSolver object extra_stepcontrol_args : dict extra arguments to specify on the spitfire.time.StepControl object save_first_and_last_only : bool whether or not to retain all data (False, default) or only the first and last solutions Returns ------- a library containing temperature, mass fractions, and density (isochoric) or pressure (isobaric) over time """ def post_step_callback(t, state, *args): state[state < 0.] = 0. return state integrator_args = {'stop_criteria': stop_criteria} if stop_at_time is not None: integrator_args.update({'stop_at_time': stop_at_time}) if stop_at_steady is not None: integrator_args.update({'stop_at_steady': stop_at_steady}) integrator_args.update(extra_integrator_args) # build the step controller and set attributes step_control_args = {'first_step': first_time_step, 'max_step': max_time_step, 'target_error': transient_tolerance} step_control_args.update(extra_stepcontrol_args) step_controller = stepcontrol_type(**step_control_args) # build the nonlinear solver and set attributes nonlinear_solver_args = {'evaluate_jacobian_every_iter': False, 'norm_weighting': 1. / self._variable_scales, 'tolerance': nonlinear_solve_tolerance} nonlinear_solver_args.update(extra_nlsolver_args) newton_solver = nlsolver_type(**nonlinear_solver_args) # build the stepper method and set attributes stepper_args = {'nonlinear_solver': newton_solver, 'norm_weighting': 1. / self._variable_scales} stepper_args.update(extra_stepper_args) stepper = stepper_type(**stepper_args) # build the rhs and projector methods and do the integration if self._configuration == 'isobaric': def rhs_method(time, state): k = np.zeros(self._n_equations) self._update_mass_transfer_parameters(time) self._update_heat_transfer_parameters(time) self._griffon.reactor_rhs_isobaric(state, self._initial_pressure, self._tf_value, self._yf_value, self._tau_value, self._tc_value, self._tr_value, self._cc_value, self._re_value, self._surface_area_to_volume, self._heat_transfer_option, self._is_open, k) return k def jac_method(state): k = np.zeros(self._n_equations) j = np.zeros(self._n_equations * self._n_equations) self._griffon.reactor_jac_isobaric(state, self._initial_pressure, self._tf_value, self._yf_value, self._tau_value, self._tc_value, self._tr_value, self._cc_value, self._re_value, self._surface_area_to_volume, self._heat_transfer_option, self._is_open, self._rates_sensitivity_option, self._sensitivity_transform_option, k, j) return j.reshape((self._n_equations, self._n_equations), order='F') elif self._configuration == 'isochoric': def rhs_method(time, state): k = np.zeros(self._n_equations) self._update_mass_transfer_parameters(time) self._update_heat_transfer_parameters(time) self._griffon.reactor_rhs_isochoric(state, self._rf_value, self._tf_value, self._yf_value, self._tau_value, self._tc_value, self._tr_value, self._cc_value, self._re_value, self._surface_area_to_volume, self._heat_transfer_option, self._is_open, k) return k def jac_method(state): k = np.zeros(self._n_equations) j = np.zeros(self._n_equations * self._n_equations) self._griffon.reactor_jac_isochoric(state, self._rf_value, self._tf_value, self._yf_value, self._tau_value, self._tc_value, self._tr_value, self._cc_value, self._re_value, self._surface_area_to_volume, self._heat_transfer_option, self._is_open, self._rates_sensitivity_option, k, j) return j.reshape((self._n_equations, self._n_equations), order='F') setup_wrapper = getattr(self, '_' + linear_solver + '_setup_wrapper') setup_method = lambda t, state, prefactor: setup_wrapper(jac_method, state, prefactor) solve_method = self._lapack_solve if linear_solver == 'lapack' else self._superlu_solve output = odesolve(right_hand_side=rhs_method, initial_state=self._current_state, initial_time=self._current_time, step_size=step_controller, method=stepper, linear_setup=setup_method, linear_solve=solve_method, minimum_time_step_count=minimum_time_step_count, linear_setup_rate=maximum_steps_per_jacobian, verbose=write_log, log_rate=log_rate, extra_logger_log=self._extra_logger_log, extra_logger_title_line1=self._extra_logger_title_line1, extra_logger_title_line2=self._extra_logger_title_line2, norm_weighting=1. / self._variable_scales, post_step_callback=post_step_callback, save_each_step=not save_first_and_last_only, **integrator_args) if save_first_and_last_only: current_state, current_time, time_step_size = output self._current_state = np.copy(current_state) self._current_time = np.copy(current_time) states = np.zeros((1, current_state.size)) states[0, :] = current_state t = np.zeros(1) t[0] = current_time else: t, states = output self._current_state = np.copy(states[-1, :]) self._current_time = np.copy(t[-1]) time_dimension = Dimension('time', t) output_library = Library(time_dimension) if self._configuration == 'isobaric': self._current_pressure = self._initial_pressure self._current_temperature = self._current_state[0] ynm1 = self._current_state[1:] self._current_mass_fractions = hstack((ynm1, 1. - sum(ynm1))) output_library['temperature'] = states[:, 0] output_library['pressure'] = self.current_pressure + np.zeros_like(output_library['temperature']) elif self._configuration == 'isochoric': ynm1 = self._current_state[2:] self._current_mass_fractions = hstack((ynm1, 1. - sum(ynm1))) self._current_pressure = self._current_state[0] * self._current_state[1] / sum( [yi / Mi for (yi, Mi) in zip(self._current_mass_fractions.tolist(), self._mechanism.molecular_weights.tolist())]) self._current_temperature = self._current_state[1] output_library['density'] = states[:, 0] output_library['temperature'] = states[:, 1] species_names = self._mechanism.species_names output_library['mass fraction ' + species_names[-1]] = np.ones_like(output_library['temperature']) for i, s in enumerate(species_names[:-1]): output_library['mass fraction ' + s] = states[:, self._temperature_index + 1 + i] output_library['mass fraction ' + species_names[-1]] -= output_library['mass fraction ' + s] if plot is not None: t = output_library.time_values * 1.e3 T = output_library['temperature'] if plot == ['temperature']: # only plot temperature if it is the only requested variable plt.semilogx(t, T) plt.xlabel('time (ms)') plt.ylabel('Temperature (K)') plt.grid() plt.show() else: # if variables other than temperature are included in the list, plot those in a separate subplot f, (axT, axY) = plt.subplots(2, sharex=True, sharey=False) axT.semilogx(t, T) # always plot T axT.set_ylabel('Temperature (K)') axT.grid() for species_vars in plot: if species_vars is not 'temperature': # separate subplot for species mass fractions Y = output_library['mass fraction ' + species_vars] axY.loglog(t, Y, label=species_vars) axY.set_xlabel('time (ms)') axY.set_ylabel('Mass Fractions') axY.set_ylim([1.e-12, 1.e0]) axY.grid() axY.legend() plt.show() return output_library
def _expand_enthalpy_defect_dimension_steady(chi_st, managed_dict, flamelet_specs, table_dict, h_stoich_spacing, verbose, input_integration_args, solver_verbose): flamelet_specs.initial_condition = table_dict[chi_st]['adiabatic_state'] flamelet_specs.stoich_dissipation_rate = chi_st flamelet_specs.heat_transfer = 'nonadiabatic' flamelet_specs.scale_heat_loss_by_temp_range = False flamelet_specs.scale_convection_by_dissipation = False flamelet_specs.use_linear_ref_temp_profile = True flamelet_specs.radiative_emissivity = 0. flamelet_specs.convection_coefficient = 0. flamelet = Flamelet(flamelet_specs) first = True refine_before_extinction = False extinguished = False extinguished_first = False maxT = -1 state_old = np.copy(flamelet.current_interior_state) hval = 0. dh = 1.e-1 diff_target = 1e-1 diff_norm = 1e-1 hval_max = 1.e10 solutions = [] hvalues = [] hvalues.append(hval) solutions.append(dict()) for p in table_dict[chi_st]: if p != 'adiabatic_state': solutions[-1][p] = table_dict[chi_st][p] current_state = table_dict[chi_st]['adiabatic_state'] cput0000 = perf_counter() while first or (not extinguished and hval < hval_max): hval += dh if first: first = False flamelet_specs.convection_coefficient = hval flamelet_specs.initial_condition = current_state flamelet = Flamelet(flamelet_specs) g_library = flamelet.compute_steady_state(verbose=solver_verbose) current_state = flamelet.current_interior_state maxT = np.max(current_state) diff_norm = np.max( np.abs(current_state - state_old) / (np.abs(current_state) + 1.e-4)) extinguished = maxT < ( np.max([flamelet.oxy_stream.T, flamelet.fuel_stream.T]) + 10.) if (extinguished and (not extinguished_first)) and refine_before_extinction: extinguished_first = True extinguished = False hval -= dh dh *= 0.1 diff_target *= 0.1 current_state = state_old.copy() continue state_old = np.copy(current_state) dh *= np.min([np.max([np.sqrt(diff_target / diff_norm), 0.1]), 2.]) hvalues.append(hval) solutions.append(dict()) for p in g_library.props: solutions[-1][p] = g_library[p].ravel() z_dim = Dimension(_mixture_fraction_name, flamelet.mixfrac_grid) h_dim = Dimension(_enthalpy_defect_name + _stoich_suffix, np.array(hvalues)) steady_lib = Library(z_dim, h_dim) steady_lib.extra_attributes['mech_spec'] = flamelet_specs.mech_spec for p in table_dict[chi_st]: if p != 'adiabatic_state': steady_lib[p] = steady_lib.get_empty_dataset() for ig, sol in enumerate(solutions): for p in sol: steady_lib[p][:, ig] = sol[p].ravel() indices = [0] z = flamelet.mixfrac_grid z_st = flamelet.mechanism.stoich_mixture_fraction(flamelet.fuel_stream, flamelet.oxy_stream) h_tz = sca.compute_specific_enthalpy(flamelet_specs.mech_spec, steady_lib)['enthalpy'] h_ad = h_tz[:, 0] nz, nt = h_tz.shape last_hst = interp1d(z, h_ad)(z_st) for i in range(nt - 1): this_hst = interp1d(z, h_tz[:, i])(z_st) if last_hst - this_hst > h_stoich_spacing: indices.append(i) last_hst = this_hst for i in indices: defect = h_tz[:, i] - h_ad gst = float(interp1d(z, defect)(z_st)) this_data = dict() this_data['enthalpy_defect'] = np.copy(defect) this_data['enthalpy_cons'] = np.copy(h_ad) this_data['enthalpy'] = np.copy(h_tz[:, i]) this_data[_mixture_fraction_name] = flamelet.mixfrac_grid for q in steady_lib.props: this_data[q] = steady_lib[q][:, i] managed_dict[(chi_st, gst)] = this_data dcput = perf_counter() - cput0000 if verbose: print('chi_st = {:8.1e} 1/s converged in {:6.2f} s'.format( chi_st, dcput), flush=True)
def build_adiabatic_slfm_library(flamelet_specs, diss_rate_values=np.logspace(-3, 2, 16), diss_rate_ref='stoichiometric', verbose=True, solver_verbose=False, _return_intermediates=False, include_extinguished=False): """Build a flamelet library with an adiabatic strained laminar flamelet model Parameters ---------- flamelet_specs : dictionary or FlameletSpec instance data for the mechanism, streams, mixture fraction grid, etc. diss_rate_values : np.array reference dissipation rate values in the table (note that if the flamelet extinguishes at any point, the extinguished flamelet and larger dissipation rates are not included in the library unless the include_extinguished argument is set to True) diss_rate_ref : str the reference point of the specified dissipation rate values, either 'stoichiometric' or 'maximum' verbose : bool whether or not to show progress of the library construction include_extinguished : bool whether or not to include extinguished states in the output table, if encountered in the provided range of dissipation rates, off by default Returns ------- library : spitfire.chemistry.library.Library instance the structured chemistry library """ if isinstance(flamelet_specs, dict): flamelet_specs = FlameletSpec(**flamelet_specs) m = flamelet_specs.mech_spec fuel = flamelet_specs.fuel_stream oxy = flamelet_specs.oxy_stream flamelet_specs.initial_condition = 'equilibrium' if diss_rate_ref == 'maximum': flamelet_specs.max_dissipation_rate = 0. else: flamelet_specs.stoich_dissipation_rate = 0. cput00 = _write_library_header('adiabatic SLFM', m, fuel, oxy, verbose) f = Flamelet(flamelet_specs) table_dict = dict() nchi = diss_rate_values.size suffix = _stoich_suffix if diss_rate_ref == 'stoichiometric' else '_max' x_values = list() for idx, chival in enumerate(diss_rate_values): if diss_rate_ref == 'maximum': flamelet_specs.max_dissipation_rate = chival else: flamelet_specs.stoich_dissipation_rate = chival flamelet = Flamelet(flamelet_specs) if verbose: print(f'{idx + 1:4}/{nchi:4} (chi{suffix} = {chival:8.1e} 1/s) ', end='', flush=True) cput0 = perf_counter() x_library = flamelet.compute_steady_state(tolerance=1.e-6, verbose=solver_verbose, use_psitc=True) dcput = perf_counter() - cput0 if np.max(flamelet.current_temperature - flamelet.linear_temperature ) < 10. and not include_extinguished: if verbose: print( ' extinction detected, stopping. The extinguished state will not be included in the table.' ) break else: if verbose: print( f' converged in {dcput:6.2f} s, T_max = {np.max(flamelet.current_temperature):6.1f}' ) z_st = flamelet.mechanism.stoich_mixture_fraction( flamelet.fuel_stream, flamelet.oxy_stream) chi_st = flamelet._compute_dissipation_rate( np.array([z_st]), flamelet._max_dissipation_rate, flamelet._dissipation_rate_form)[0] x_values.append(chi_st) table_dict[chi_st] = dict() for k in x_library.props: table_dict[chi_st][k] = x_library[k].ravel() flamelet_specs.initial_condition = flamelet.current_interior_state if _return_intermediates: table_dict[chi_st]['adiabatic_state'] = np.copy( flamelet.current_interior_state) if _return_intermediates: _write_library_footer(cput00, verbose) return table_dict, f.mixfrac_grid, np.array(x_values) else: z_dim = Dimension(_mixture_fraction_name, f.mixfrac_grid) x_dim = Dimension(_dissipation_rate_name + _stoich_suffix, np.array(x_values)) output_library = Library(z_dim, x_dim) output_library.extra_attributes['mech_spec'] = m for quantity in table_dict[chi_st]: output_library[quantity] = output_library.get_empty_dataset() for ix, x in enumerate(x_values): output_library[quantity][:, ix] = table_dict[x][quantity] _write_library_footer(cput00, verbose) return output_library
def _build_nonadiabatic_defect_unstrained_library(initialization, flamelet_specs, n_defect_st=16, verbose=True): flamelet_specs = FlameletSpec(**flamelet_specs) if isinstance( flamelet_specs, dict) else copy.copy(flamelet_specs) m = flamelet_specs.mech_spec fuel = flamelet_specs.fuel_stream oxy = flamelet_specs.oxy_stream z_st = m.stoich_mixture_fraction(fuel, oxy) flamelet_specs.initial_condition = initialization flamelet = Flamelet(flamelet_specs) # compute the extreme enthalpy defect state_ad = flamelet.initial_interior_state adiabatic_lib = flamelet.make_library_from_interior_state(state_ad) enthalpy_ad = sca.compute_specific_enthalpy(m, adiabatic_lib)['enthalpy'] z_interior = flamelet.mixfrac_grid[1:-1] state_cooled_eq = state_ad.copy() state_cooled_eq[::m. n_species] = z_interior * fuel.T + (1 - z_interior) * oxy.T cooled_lib = flamelet.make_library_from_interior_state(state_cooled_eq) enthalpy_cooled_eq = sca.compute_specific_enthalpy(m, cooled_lib)['enthalpy'] z = flamelet.mixfrac_grid h_ad_st = interp1d(z, enthalpy_ad)(z_st) h_ce_st = interp1d(z, enthalpy_cooled_eq)(z_st) defect_ext = h_ad_st - h_ce_st # build the library with equilibrium solutions at with enthalpies offset by the triangular defect form defect_range = np.linspace(-defect_ext, 0, n_defect_st)[::-1] z_dim = Dimension(_mixture_fraction_name, flamelet.mixfrac_grid) g_dim = Dimension(_enthalpy_defect_name + _stoich_suffix, defect_range) output_library = Library(z_dim, g_dim) output_library.extra_attributes['mech_spec'] = m for p in adiabatic_lib.props: output_library[p] = output_library.get_empty_dataset() output_library['enthalpy_defect'] = output_library.get_empty_dataset() output_library['enthalpy_cons'] = output_library.get_empty_dataset() output_library['enthalpy'] = output_library.get_empty_dataset() output_library[_mixture_fraction_name] = output_library.get_empty_dataset() fz = z.copy() fz[z <= z_st] = z[z <= z_st] / z_st fz[z > z_st] = (1 - z[z > z_st]) / (1 - z_st) ns = m.n_species g_library = flamelet.make_library_from_interior_state( flamelet.initial_interior_state) for ig in range(n_defect_st): defected_enthalpy = enthalpy_ad + defect_range[ig] * fz for iz in range(1, z.size - 1): y = np.zeros(ns) for ispec in range(ns): y[ispec] = g_library['mass fraction ' + m.species_names[ispec]][iz] m.gas.HPY = defected_enthalpy[iz], flamelet.pressure, y if initialization == 'equilibrium': m.gas.equilibrate('HP') g_library['temperature'][iz] = m.gas.T for ispec in range(ns): g_library['mass fraction ' + m.species_names[ispec]][iz] = m.gas.Y[ispec] for p in g_library.props: if p != 'defected_enthapy': output_library[p][:, ig] = g_library[p].ravel() output_library['enthalpy_defect'][:, ig] = defected_enthalpy - enthalpy_ad output_library['enthalpy_cons'][:, ig] = enthalpy_ad output_library['enthalpy'][:, ig] = defected_enthalpy output_library[ _mixture_fraction_name][:, ig] = flamelet.mixfrac_grid.ravel() return output_library
def apply_mixing_model(library, mixing_spec, added_suffix=_mean_suffix, num_procs=1, verbose=False): """Take an existing tabulated chemistry library and incorporate subgrid variation in each reaction variable with a presumed PDF model. This requires statistical independence of the reaction variables. If a reaction variable is not included in the mixing_spec dictionary, a delta PDF is presumed for it. Parameters ---------- library : spitfire.Library an existing library from a reaction model mixing_spec : str a dictionary mapping reaction variable names to PDFSpec objects that describe the variable's presumed PDF added_suffix : str string to add to each name, for instance '_mean' if this is the first PDF convolution, or '' if a successive convolution num_procs : Int how many processors over which to distribute the parallel extinction solves verbose : bool whether or not (default False) to print information about the PDF convolution Returns ------- library : spitfire.Library instance the library with any nontrivial variance dimensions added """ require_pytabprops('apply_mixing_model') for dim in mixing_spec: if dim not in library.dim_names: raise KeyError( f'Invalid variable name \"{dim}\" for library with names {library.dim_names}' ) mixing_spec = copy.copy(mixing_spec) for dim in library.dims: if dim.name not in mixing_spec: mixing_spec[dim.name] = 'delta' for dim in mixing_spec: if mixing_spec[dim] == 'delta': mixing_spec[dim] = PDFSpec('delta', variance_values=np.array([0.])) for dim in mixing_spec: if (mixing_spec[dim].variance_values is None and mixing_spec[dim].scaled_variance_values is None and mixing_spec[dim].pdf == 'delta'): mixing_spec[dim] = PDFSpec('delta', variance_values=np.array([0.])) extensions = [] for i, key in enumerate(mixing_spec): extensions.append({ 'variable_name': key, 'pdf_spec': mixing_spec[key], 'num_procs': num_procs, 'verbose': verbose }) def get_size(element): pdfspec = element['pdf_spec'] if pdfspec.pdf == 'delta': return 0 else: if pdfspec.variance_values is not None: return pdfspec.variance_values.size else: return pdfspec.scaled_variance_values.size extensions = sorted(extensions, key=lambda element: get_size(element)) turb_lib = Library.copy(library) for i, ext in enumerate(extensions): ext['library'] = turb_lib ext['added_suffix'] = '' if i < len(extensions) - 1 else added_suffix turb_lib = _apply_presumed_pdf_1var(**ext) if get_size(ext) == 1 and ext['pdf_spec'].pdf != 'delta': turb_lib = Library.squeeze( turb_lib[tuple([slice(None)] * (len(turb_lib.dims) - 1) + [slice(0, 1)])]) if 'mixing_spec' in turb_lib.extra_attributes: turb_lib.extra_attributes['mixing_spec'].update(mixing_spec) else: turb_lib.extra_attributes['mixing_spec'] = mixing_spec return turb_lib