def write_T_flow(T=None, P=None, Q=None, abyv=None, conditions=None, filename=None, float_format='.3E', newline='\n', column_delimiter=' '): """Writes the T_flow.inp Chemkin file Parameters ---------- T : list of float Temperatures in K P : list of float Pressures in atm Q : list of float Volumetric flow rate in cm3/s abyv : list of float Catalyst area to reactor volume ratio in 1/cm filename : str, optional Name of the file. If not specified, returns file as str float_format : str, optional Format to write floating point numbers. Default is '.3E' (i.e. scientific notation rounded to 3 decimal places) newline : str, optional Newline character. Default is the Linux newline character column_delimiter : str, optional Delimiter for columns. Default is ' ' Returns ------- lines_out : str T_flow lines as a string if ``filename`` is None """ line_field = '{:%s}{}{:%s}{}{:%s}{}{:%s} !{:<3}' % ( float_format, float_format, float_format, float_format) lines = [ _get_file_timestamp(comment_char='! '), '!Conditions for each reaction run', '!Only used when MultiInput in tube.inp is set to "T"', '!T[K] {0}P[atm] {0}Q[cm3/s] {0}abyv[cm-1]{0}Run #'.format( column_delimiter) ] for i, (T_i, P_i, Q_i, abyv_i) in enumerate(zip(T, P, Q, abyv)): lines.append( line_field.format(T_i, column_delimiter, P_i, column_delimiter, Q_i, column_delimiter, abyv_i, i + 1)) lines.append('EOF') lines_out = '\n'.join(lines) if filename is not None: with open(filename, 'w', newline=newline) as f_ptr: f_ptr.write(lines_out) else: return lines_out
def write_yaml(reactor_type=None, mode=None, nodes=None, V=None, T=None, P=None, A=None, L=None, cat_abyv=None, flow_rate=None, residence_time=None, mass_flow_rate=None, end_time=None, transient=None, stepping=None, init_step=None, step_size=None, atol=None, rtol=None, full_SA=None, reactions_SA=None, species_SA=None, phases=None, reactor=None, inlet_gas=None, multi_T=None, multi_P=None, multi_flow_rate=None, output_format=None, solver=None, simulation=None, multi_input=None, misc=None, units=None, filename=None, yaml_options={ 'default_flow_style': False, 'indent': 4 }, newline='\n'): """Writes the reactor options in a YAML file for OpenMKM. Parameters ---------- reactor_type : str Type of reactor. Supported options include: - pfr - pfr_0d - cstr - batch Value written to ``reactor.type``. mode : str Operation of reactor. Supported options include: - Isothermal - Adiabatic Value written to ``reactor.mode``. nodes : int Number of nodes to use if ``reactor_type`` is 'pfr_0d'. Value written to ``reactor.nodes`` V : float or str Volume of reactor. Value written to ``reactor.volume``. Units of length^3. See Notes section regarding unit specification. T : float Temperature (in K) of reactor. Value written to ``reactor.temperature``. P : float or str Pressure of reactor. Value written to ``reactor.pressure``. Units of pressure. See Notes section regarding unit specification. A : float or str Surface area of reactor. Value written to ``reactor.area``. Units of length^2. See Notes section regarding unit specification. L : float or str Length of reactor. Value written to ``reactor.length``. Units of length. See Notes section regarding unit specification. cat_abyv : float or str Catalyst surface area to volume ratio. Value written to ``reactor.cat_abyv``. Units of 1/length. See Notes section regarding unit specification. flow_rate : float or str Volumetric flow rate of inlet. Value written to ``inlet_gas.flow_rate``. Units of length^3/time. See Notes section regarding unit specification. residence_time : float or str Residence time of reactor. Value written to ``inlet_gas.residence_time``. Not required if ``flow_rate`` or ``mass_flow_rate`` already specified. Units of time. See Notes section regarding unit specification. mass_flow_rate : float or str Mass flow rate of inlet. Value written to ``inlet_gas.mass_flow_rate``. Units of mass^3/time. See Notes section regarding unit specification. end_time : float or str Reactor simulation time. For continuous reactors, the system is assumed to reach steady state by this time. Value written to ``simulation.end_time``. Units of time. See Notes section regarding unit specification. transient : bool If True, transient operation results are saved. Otherwise, transient files are left blank. Value written to ``simulation.transient``. stepping : str Time steps taken to simulate reactor. Supported options include: - logarithmic - regular Value written to ``simulation.stepping``. init_step : float or str Initial step to take. Value written to ``simulation.init_step``. step_size : float or str If ``stepping`` is logarithmic, represents the ratio between the next step and the current step. If ``stepping`` is regular, represents the time between the next step and the current step in units of time. Value written to simulation.step_size. See Notes section regarding unit specification. atol : float Absolute tolerance for solver. Value written to ``simulation.solver.atol``. rtol : float Relative tolerance for solver. Value written to ``simulation.solver.rtol``. full_SA : bool If True, OpenMKM will do a full sensitivity analysis using the Fisher Information Matrix (FIM). Value written to ``simulation.sensitivity.full``. reactions_SA : list of str or list of :class:`~pmutt.omkm.reaction.SurfaceReaction` obj List of reactions to perturb using local sensitivity analysis (LSA). If a list of :class:`~pmutt.omkm.reaction.SurfaceReaction` is given, the ``id`` attribute will be used. species_SA : list of str or list of :class:`~pmutt.empirical.EmpiricalBase` obj List of species to perturb using local sensitivity analysis (LSA). If a list of :class:`~pmutt.empirical.EmpiricalBase` is given, the ``name`` attribute will be used. phases : list of ``Phase`` objects Phases present in reactor. Each phase should have the ``name`` and ``initial_state`` attribute. reactor : dict Generic dictionary for reactor to specify values not supported by ``write_yaml``. inlet_gas : dict Generic dictionary for inlet_gas to specify values not supported by ``write_yaml``. multi_T : list of float Multiple temperatures (in K) of reactor. Value written to ``simulation.multi_input.temperature``. multi_P : list of float/str Multiple pressures of reactor. Value written to ``simulation.multi_input.pressure``. Units of pressure. See Notes section regarding unit specification. multi_flow_rate : list of float/str Multiple flow rates to run the model. Value written to ``simulation.multi_input.flow_rate``. Units of length3/time. See Notes section regarding unit specification. output_format : str Format for output files. Supported options include: - CSV - DAT Value written to ``simulation.output_format``. misc : dict Generic dictionary for any parameter specified at the top level. solver : dict Generic dictionary for solver to specify values not supported by ``write_yaml``. simulation : dict Generic dictionary for simultaion to specify values not supported by ``write_yaml``. multi_input : dict Generic dictionary for multi_input to specify values not supported by ``write_yaml``. units : dict or :class:`~pmutt.omkm.units.Unit` object, optional Units used for file. If a dict is inputted, the key is the quantity and the value is the unit. If not specified, uses the default units of :class:`~pmutt.omkm.units.Unit`. filename: str, optional Filename for the input.cti file. If not specified, returns file as str. yaml_options : dict Options to pass when converting the parameters to YAML format. See `PyYAML documentation`_ for ``dump`` for available options. Returns ------- lines_out : str If ``filename`` is None, CTI file is returned Notes ----- **Units** If ``units`` is not specified, all values in file are assumed to be SI units. If ``units`` is specified, all values inputted are assumed to be in the units specified by ``units``. If a particular unit set is desired for a value, a str can be inputted with the form quantity="<<value>> <<desired units>>" where ``value`` is float-like and ``desired units`` is a string. For example, flow_rate="1 cm3/s". See https://vlachosgroup.github.io/openmkm/input for the most up-to-date supported values. **Generic Dictionaries** Also, values in generic dictionaries (i.e. ``reactor``, ``inlet_gas``, ``simulation``) will ben written preferentially over arguments passed. i.e. The value in ``reactor['temperature']`` will be written instead of ``T``. .. _`PyYAML Documentation`: https://pyyaml.org/wiki/PyYAMLDocumentation """ lines = [ _get_file_timestamp(comment_char='# '), '# See documentation for OpenMKM YAML file here:', '# https://vlachosgroup.github.io/openmkm/input' ] '''Initialize units''' if isinstance(units, dict): units = Units(**units) '''Organize reactor parameters''' if reactor is None: reactor = {} reactor_params = [ _Param('type', reactor_type, None), _Param('mode', mode, None), _Param('nodes', nodes, None), _Param('volume', V, '_length3'), _Param('area', A, '_length2'), _Param('length', L, '_length'), _Param('cat_abyv', cat_abyv, '/_length') ] # Process temperature if T is not None: reactor_params.append(_Param('temperature', T, None)) elif multi_T is not None: reactor_params.append(_Param('temperature', multi_T[0], None)) # Process pressure if P is not None: reactor_params.append(_Param('pressure', P, '_pressure')) elif multi_P is not None: reactor_params.append(_Param('pressure', multi_P[0], '_pressure')) for parameter in reactor_params: _assign_yaml_val(parameter, reactor, units) if inlet_gas is None: inlet_gas = {} inlet_gas_params = [ _Param('residence_time', residence_time, '_time'), _Param('mass_flow_rate', mass_flow_rate, '_mass/_time') ] # Process flow rate if flow_rate is not None: inlet_gas_params.append( _Param('flow_rate', flow_rate, '_length3/_time')) elif multi_flow_rate is not None: inlet_gas_params.append( _Param('flow_rate', multi_flow_rate[0], '_length3/_time')) '''Process inlet gas parameters''' for parameter in inlet_gas_params: _assign_yaml_val(parameter, inlet_gas, units) '''Organize solver parameters''' if solver is None: solver = {} solver_params = [_Param('atol', atol, None), _Param('rtol', rtol, None)] for parameter in solver_params: _assign_yaml_val(parameter, solver, units) '''Organize multi parameters''' if multi_input is None: multi_input = {} # Ensure multi quantities are in correct type if _is_iterable(multi_T): multi_T = list(multi_T) if _is_iterable(multi_P): multi_P = list(multi_P) if _is_iterable(multi_flow_rate): multi_flow_rate = list(multi_flow_rate) multi_input_params = [ _Param('temperature', multi_T, None), _Param('pressure', multi_P, '_pressure'), _Param('flow_rate', multi_flow_rate, '_length3/_time') ] for parameter in multi_input_params: _assign_yaml_val(parameter, multi_input, units) '''Organize sensitivity parameters''' sensitivity = {} if reactions_SA is None: reactions_SA_names = None else: reactions_SA_names = [] for reaction in reactions_SA: if isinstance(reaction, str): name = reaction else: try: name = reaction.id except AttributeError: err_msg = ('Unable to write reaction id to reactor YAML ' 'file because object is invalid type ({}). ' 'Expected str or ' 'pmutt.omkm.reaction.SurfaceReaction type with ' 'id attribute assigned.' ''.format(type(reaction))) raise TypeError(err_msg) reactions_SA_names.append(name) if species_SA is None: species_SA_names = None else: species_SA_names = [] for ind_species in species_SA: if isinstance(ind_species, str): name = ind_species else: try: name = ind_species.name except AttributeError: err_msg = ('Unable to write species name to reactor YAML ' 'file because object is invalid type ({}). ' 'Expected str or ' 'pmutt.empirical.EmpiricalBase type with ' 'name attribute assigned.' ''.format(type(ind_species))) raise TypeError(err_msg) species_SA_names.append(name) sensitivity_params = (_Param('full', full_SA, None), _Param('reactions', reactions_SA_names, None), _Param('species', species_SA_names, None)) for parameter in sensitivity_params: _assign_yaml_val(parameter, sensitivity, units) '''Organize simulation parameters''' if simulation is None: simulation = {} simulation_params = (_Param('end_time', end_time, '_time'), _Param('transient', transient, None), _Param('stepping', stepping, None), _Param('step_size', step_size, None), _Param('init_step', init_step, None), _Param('output_format', output_format, None)) for parameter in simulation_params: _assign_yaml_val(parameter, simulation, units) if len(solver) > 0: _assign_yaml_val(_Param('solver', solver, None), simulation, units) if len(multi_input) > 0: _assign_yaml_val(_Param('multi_input', multi_input, None), simulation, units) if len(sensitivity) > 0: _assign_yaml_val(_Param('sensitivity', sensitivity, None), simulation, units) '''Organize phase parameters''' if phases is not None: if isinstance(phases, dict): phases_dict = phases.copy() elif isinstance(phases, list): phases_dict = {} for phase in phases: phase_info = {'name': phase.name} # Assign intial state if available if phase.initial_state is not None: initial_state_str = '"' for species, mole_frac in phase.initial_state.items(): initial_state_str += '{}:{}, '.format( species, mole_frac) initial_state_str = '{}"'.format(initial_state_str[:-2]) phase_info['initial_state'] = initial_state_str # Assign phase type if isinstance(phase, IdealGas): phase_type = 'gas' elif isinstance(phase, StoichSolid): phase_type = 'bulk' elif isinstance(phase, InteractingInterface): phase_type = 'surfaces' try: phases_dict[phase_type].append(phase_info) except KeyError: phases_dict[phase_type] = [phase_info] # If only one entry for phase type, reassign it. for phase_type, phases in phases_dict.items(): if len(phases) == 1 and phase_type != 'surfaces': phases_dict[phase_type] = phases[0] '''Assign misc values''' if misc is None: misc = {} yaml_dict = misc.copy() '''Assign values to overall YAML dict''' headers = (('reactor', reactor), ('inlet_gas', inlet_gas), ('simulation', simulation), ('phases', phases_dict)) for header in headers: if len(header[1]) > 0: yaml_dict[header[0]] = header[1] '''Convert dictionary to YAML str''' yaml_str = yaml.dump(yaml_dict, **yaml_options) # Remove redundant quotes yaml_str = yaml_str.replace('\'', '') lines.append(yaml_str) '''Write to file''' lines_out = '\n'.join(lines) if filename is not None: with open(filename, 'w', newline=newline) as f_ptr: f_ptr.write(lines_out) else: # Or return as string return lines_out
def write_thermo_yaml(phases=None, species=None, reactions=None, lateral_interactions=None, units=None, filename=None, T=300., P=1., newline='\n', ads_act_method='get_H_act', yaml_options={ 'default_flow_style': None, 'indent': 2, 'sort_keys': False, 'width': 79 }): """Writes the units, phases, species, lateral interactions, reactions and additional options in the CTI format for OpenMKM Parameters ---------- phases : list of :class:`~pmutt.omkm.phase.Phase` objects Phases to write in YAML file. The species should already be assigned. species : list of :class:`~pmutt.empirical.nasa.Nasa`, :class:`~pmutt.empirical.nasa.Nasa9` or :class:`~pmutt.empirical.shomate.Shomate` Species to write in YAML file. reactions : list of :class:`~pmutt.omkm.reaction.SurfaceReaction` Reactions to write in YAML file. lateral_interactions : list of :class:`~pmutt.mixture.cov.PiecewiseCovEffect` objects, optional Lateral interactions to include in YAML file. Default is None. units : dict or :class:`~pmutt.omkm.units.Unit` object, optional Units to write file. If a dict is inputted, the key is the quantity and the value is the unit. If not specified, uses the default units of :class:`~pmutt.omkm.units.Unit`. filename: str, optional Filename for the input.yaml file. If not specified, returns file as str. T : float, optional Temperature in K. Default is 300 K. P : float, optional Pressure in atm. Default is 1 atm. newline : str, optional Type of newline to use. Default is Linux newline ('\\n') ads_act_method : str, optional Activation method to use for adsorption reactions. Accepted options include 'get_H_act' and 'get_G_act'. Default is 'get_H_act'. Returns ------- lines_out : str If ``filename`` is None, CTI file is returned. """ lines = [ _get_file_timestamp(comment_char='# '), '# See documentation for OpenMKM YAML file here:', '# https://vlachosgroup.github.io/openmkm/input', ] yaml_dict = {} '''Organize units units''' if units is None: units = Units() elif isinstance(units, dict): units = Units(**units) units_out = units.to_omkm_yaml() '''Pre-assign IDs for lateral interactions so phases can be written''' if lateral_interactions is not None: interactions_out = [] i = 0 for lat_interaction in lateral_interactions: if lat_interaction.name is None: lat_interaction.name = 'i_{:04d}'.format(i) i += 1 interaction_dict = lat_interaction.to_omkm_yaml(units=units) interactions_out.append(interaction_dict) '''Pre-assign IDs for reactions so phases can be written''' beps = [] if reactions is not None: reactions_out = [] i = 0 for reaction in reactions: # Assign reaction ID if not present if reaction.id is None: reaction.id = 'r_{:04d}'.format(i) i += 1 # Write reaction reaction_dict = reaction.to_omkm_yaml(units=units, T=T) reactions_out.append(reaction_dict) # Add unique BEP relationship if any try: bep = reaction.bep except AttributeError: pass else: if bep is not None and bep not in beps: beps.append(bep) '''Write phases''' if phases is not None: phases_out = [] for phase in phases: phase_dict = _force_pass_arguments(phase.to_omkm_yaml, units=units) phases_out.append(phase_dict) # yaml_dict['phases'] = phases_out '''Write species''' if species is not None: species_out = [] for ind_species in species: ind_species_dict = _force_pass_arguments(ind_species.to_omkm_yaml, units=units) species_out.append(ind_species_dict) # yaml_dict['species'] = species_out '''Organize BEPs''' if len(beps) > 0: beps_out = [] i = 0 for bep in beps: # Assign name if necessary if bep.name is None: bep.name = 'b_{:04d}'.format(i) i += 1 bep_dict = _force_pass_arguments(bep.to_omkm_yaml, units=units) beps_out.append(bep_dict) # yaml_dict['beps'] = beps_out '''Organize fields''' fields = ( 'units', 'phases', 'species', 'reactions', 'beps', 'interactions', ) for field in fields: try: val = locals()['{}_out'.format(field)] except: pass else: # Create a YAML string yaml_str = yaml.dump(data={field: val}, stream=None, **yaml_options) lines.extend([ '', '#' + '-' * 79, '# {}'.format(field.upper()), '#' + '-' * 79, yaml_str ]) # yaml_dict[field] = val # Convert to YAML format # yaml_str = yaml.dump(data=yaml_dict, stream=None, **yaml_options) # Remove redundant quotes # yaml_str = yaml_str.replace('\'', '') # lines.append(yaml_str) lines_out = '\n'.join(lines) # Remove redundant strings lines_out = lines_out.replace('\'', '') # Add spacing between list elements lines_out = lines_out.replace('\n-', '\n\n-') if filename is not None: filename = Path(filename) with open(filename, 'w', newline=newline) as f_ptr: f_ptr.write(lines_out) else: # Or return as string return lines_out
def write_cti(phases=None, species=None, reactions=None, lateral_interactions=None, units=None, filename=None, T=300., P=1., newline='\n', use_motz_wise=False, ads_act_method='get_H_act', write_xml=True): """Writes the units, phases, species, lateral interactions, reactions and additional options in the CTI format for OpenMKM Parameters ---------- phases : list of :class:`~pmutt.omkm.phase.Phase` objects Phases to write in CTI file. The species should already be assigned. species : list of :class:`~pmutt.empirical.nasa.Nasa`, :class:`~pmutt.empirical.nasa.Nasa9` or :class:`~pmutt.empirical.shomate.Shomate` Species to write in CTI file. reactions : list of :class:`~pmutt.omkm.reaction.SurfaceReaction` Reactions to write in CTI file. lateral_interactions : list of :class:`~pmutt.mixture.cov.PiecewiseCovEffect` objects, optional Lateral interactions to include in CTI file. Default is None. units : dict or :class:`~pmutt.omkm.units.Unit` object, optional Units to write file. If a dict is inputted, the key is the quantity and the value is the unit. If not specified, uses the default units of :class:`~pmutt.omkm.units.Unit`. filename: str, optional Filename for the input.cti file. If not specified, returns file as str. T : float, optional Temperature in K. Default is 300 K. P : float, optional Pressure in atm. Default is 1 atm. newline : str, optional Type of newline to use. Default is Linux newline ('\\n') use_motz_wise : bool, optional Whether to use Motz-wise sticking coefficients or not. Default is False ads_act_method : str, optional Activation method to use for adsorption reactions. Accepted options include 'get_H_act' and 'get_G_act'. Default is 'get_H_act'. write_xml : bool, optional If True and ``filename`` is not ``None``, automatically generates an XML file with the CTI file. Returns ------- lines_out : str If ``filename`` is None, CTI file is returned. """ lines = [ _get_file_timestamp(comment_char='# '), '# See documentation for OpenMKM CTI file here:', '# https://vlachosgroup.github.io/openmkm/input' ] '''Write units''' lines.extend(['', '#' + '-' * 79, '# UNITS', '#' + '-' * 79]) if units is None: units = Units() elif isinstance(units, dict): units = Units(**units) lines.append(units.to_cti()) '''Pre-assign IDs for lateral interactions so phases can be written''' if lateral_interactions is not None: lat_inter_lines = [] i = 0 if lateral_interactions is not None: for lat_interaction in lateral_interactions: if lat_interaction.name is None: lat_interaction.name = '{:04d}'.format(i) i += 1 lat_inter_CTI = _force_pass_arguments(lat_interaction.to_cti, units=units) lat_inter_lines.append(lat_inter_CTI) '''Pre-assign IDs for reactions so phases can be written''' if reactions is not None: beps = [] reaction_lines = [] i = 0 for reaction in reactions: # Assign reaction ID if not present if reaction.id is None: reaction.id = '{:04d}'.format(i) i += 1 # Write reaction reaction_CTI = _force_pass_arguments(reaction.to_cti, units=units, T=T) reaction_lines.append(reaction_CTI) # Add unique BEP relationship if any try: bep = reaction.bep except AttributeError: pass else: if bep is not None and bep not in beps: beps.append(bep) '''Write phases''' if phases is not None: lines.extend(['', '#' + '-' * 79, '# PHASES', '#' + '-' * 79]) for phase in phases: phase_CTI = _force_pass_arguments(phase.to_cti, units=units) lines.append(phase_CTI) '''Write species''' if species is not None: lines.extend(['', '#' + '-' * 79, '# SPECIES', '#' + '-' * 79]) for ind_species in species: ind_species_CTI = _force_pass_arguments(ind_species.to_cti, units=units) lines.append(ind_species_CTI) '''Write lateral interactions''' if lateral_interactions is not None: lines.extend( ['', '#' + '-' * 79, '# LATERAL INTERACTIONS', '#' + '-' * 79]) lines.extend(lat_inter_lines) if reactions is not None: '''Write reaction options''' lines.extend( ['', '#' + '-' * 79, '# REACTION OPTIONS', '#' + '-' * 79]) if use_motz_wise: lines.extend(['enable_motz_wise()\n']) else: lines.extend(['disable_motz_wise()\n']) '''Write reactions''' lines.extend(['', '#' + '-' * 79, '# REACTIONS', '#' + '-' * 79]) lines.extend(reaction_lines) '''Write BEP Relationships''' if len(beps) > 0: lines.extend( ['', '#' + '-' * 79, '# BEP Relationships', '#' + '-' * 79]) # Only write each BEP once i = 0 for bep in beps: bep_CTI = _force_pass_arguments(bep.to_cti, units=units) # Increment counter if necessary if bep.name is None: i += 1 lines.append(bep_CTI) '''Write to file''' lines_out = '\n'.join(lines) if filename is not None: filename = Path(filename) with open(filename, 'w', newline=newline) as f_ptr: f_ptr.write(lines_out) '''Write XML file''' if write_xml: xml_filename = filename.with_suffix('.xml').as_posix() convert(filename=filename, outName=xml_filename) else: # Or return as string return lines_out
def write_tube_mole(mole_frac_conditions, nasa_species, filename=None, float_format=' .3f', newline='\n', column_delimiter=' '): """Write tube_mole.inp Chemkin file Parameters ---------- mole_frac_conditions : list of dict Each dictionary should have the keys as the name of the species and the value as the initial mole fraction nasa_species : list of :class:`~pmutt.empirical.nasa.Nasa` objects Nasa species to find phase information filename : str, optional Name of the file. If not specified, returns file as str float_format : str, optional Format to write floating point numbers. Default is '.3E' (i.e. scientific notation rounded to 3 decimal places) newline : str, optional Newline character. Default is the Linux newline character column_delimiter : str, optional Delimiter for columns. Default is ' ' Returns ------- lines_out : str tube_mole lines as a string if ``filename`` is None """ # Prepare the float field for printing mole fractions float_field = '{:%s}' % float_format # Get relevant species unique_specie_names = set() for mole_fracs in mole_frac_conditions: for specie_name in mole_fracs.keys(): unique_specie_names.add(specie_name) # Convert nasa_species to a dict for quicker lookup unique_species = [ specie for specie in nasa_species if specie.name in unique_specie_names ] # Find length to pad species max_species_len = _get_max_species_len(species=unique_species, ignore_gas_phase=False, include_phase=True) species_padding = max_species_len + len(column_delimiter) column_line = _write_column_line(padding=species_padding, column_delimiter=column_delimiter, float_format=float_format, n_conditions=len(mole_frac_conditions)) species_lines = [] for specie in unique_species: specie_line = _get_specie_str( specie=specie, include_phase=True).ljust(species_padding) for condition in mole_frac_conditions: # If the mole fraction was not specified, assumed to be 0 try: float_str = float_field.format(condition[specie.name]) except KeyError: float_str = float_field.format(0.) specie_line = '{}{}{}'.format(specie_line, column_delimiter, float_str) species_lines.append(specie_line) lines = [ _get_file_timestamp(comment_char='! '), "!Specify the 'species/phase/' pair /(in quotes!)/ and the associated", ("!composition values. If the composition does not sum to 1 for each " "phase or"), ("!site type, it will be renormalized to 1. At the end of a " "calculation, a"), ("!file containing the complete composition and mass flux " "(the last entry) will"), ("!be generated. This file's format is completely compatible with the " "current"), "!input file and can be used to restart that calculation.", ("0 itube_restart -- will be >0 if a restart file is used or 0 " "for the first run"), '{:<3} Number of nonzero species'.format(len(unique_species)), column_line, ] lines.extend(species_lines) lines.append('EOF') lines_out = '\n'.join(lines) if filename is not None: with open(filename, 'w', newline=newline) as f_ptr: f_ptr.write(lines_out) else: return lines_out
def write_surf(reactions, sden_operation='min', filename=None, T=c.T0('K'), species_delimiter='+', reaction_delimiter='=', act_method_name='get_E_act', act_unit='kcal/mol', float_format=' .3E', stoich_format='.0f', newline='\n', column_delimiter=' ', use_mw_correction=True, **kwargs): """Writes the surf.inp Chemkin file Parameters ---------- reactions : list of :class:`~pmutt.reaction.ChemkinReaction` objects Chemkin reactions to write in surf.inp file. Purely gas-phase reactions will be ignored filename : str, optional Filename for surf.inp file. If not specified, returns file as str T : float, optional Temperature to calculate activation energy. Default is 298.15 K species_delimiter : str, optional Delimiter to separate species when writing reactions. Default is '+' reaction_delimiter : str, optional Delimiter to separate reaction sides. Default is '=' act_method_name : str, optional Name of method to use to calculate activation function act_unit : str, optional Units to calculate activation energy. Default is 'kcal/mol' float_format : str, optional String format to print floating numbers. Default is ' .3E' (i.e. scientific notation rounded to 3 decimal places with a leading space for positive numbers) stoich_format : str, optional String format to print stoichiometric coefficients. Default is '.0f' (i.e. integers) newline : str, optional Newline character. Default is the Linux newline character column_delimiter : str, optional Delimiter to separate columns. Default is ' ' use_mw_correction : bool, optional If True, uses the Motz-Wise corrections. Default is True kwargs : keyword arguments Parameters needed to calculate activation energy and preexponential factor Returns ------- lines_out : str surf.inp lines as a string if ``filename`` is None """ # Organize species by their catalyst sites cat_adsorbates = {} unique_cat_sites = [] nasa_species = reactions.get_species(include_TS=False) for specie in nasa_species.values(): # Skip gas phase species if specie.phase.upper() == 'G': continue # Skip the bulk species if specie.cat_site.bulk_specie == specie.name: continue cat_name = specie.cat_site.name try: cat_adsorbates[cat_name].append(specie) except KeyError: cat_adsorbates[cat_name] = [specie] unique_cat_sites.append(specie.cat_site) cat_site_lines = [] for cat_site in unique_cat_sites: # Add catalyst site header cat_site_name = '{}/'.format(cat_site.name) cat_site_lines.append('SITE/{:<14}SDEN/{:.5E}/'.format( cat_site_name, cat_site.site_density)) cat_site_lines.append('') # Add species adsorbed on that site for specie in cat_adsorbates[cat_site.name]: cat_site_lines.append('{}{}/{}/'.format(column_delimiter, specie.name, specie.n_sites)) cat_site_lines.append('') # Write bulk species for cat_site in unique_cat_sites: # Add bulk line cat_site_lines.append('BULK {}/{:.1f}/'.format(cat_site.bulk_specie, cat_site.density)) # Get surface reaction lines surf_reactions = \ [reaction for reaction in reactions if not reaction.gas_phase] reaction_lines = _write_reaction_lines( reactions=surf_reactions, species_delimiter=species_delimiter, reaction_delimiter=reaction_delimiter, include_TS=False, stoich_format=stoich_format, act_method_name=act_method_name, act_unit=act_unit, float_format=float_format, column_delimiter=column_delimiter, T=T, sden_operation=sden_operation, **kwargs) # Check length of lines if any([(len(reaction_line) > 80) for reaction_line in reaction_lines]): warn_msg = ('Reaction lines exceed 80 character limit when writing ' 'surf.inp. This may cause errors when Chemkin reads this ' 'file. Consider passing a different float_format, ' 'column_delimiter, or expressing the reactions more ' 'succiently to pmutt.io.chemkin.write_surf.') warn(warn_msg, UserWarning) # Preparing reaction line header mw_field = '{:<5}' if use_mw_correction: mw_str = mw_field.format('MWON') else: mw_str = mw_field.format('MWOFF') if 'oRT' in act_method_name: act_unit_str = '' else: act_unit_str = act_unit.upper() lines = [ _get_file_timestamp(comment_char='! '), '!Surface species', '!Each catalyst site has the following format:', '!SITE/[Site name]/ SDEN/[Site density in mol/cm2]/', '![Adsorbate Name]/[# of Sites occupied]/ (for every adsorbate)', '!BULK [Bulk name]/[Bulk density in g/cm3]', ] lines.extend(cat_site_lines) lines.extend([ 'END', '', '!Surface-phase reactions.', '!The reaction line has the following format:', '!REACTIONS MW[ON/OFF] [Ea units]', '!where MW stands for Motz-Wise corrections and if the Ea', ('!units are left blank, then the activation energy should ' 'be in cal/mol'), '!The rate constant expression is:', '!k = kb/h/site_den^(n-1) * (T)^beta * exp(-Ea/RT)', ('!where site_den is the site density and is the number ' 'of surface species (including empty sites)'), '!Each line has 4 columns:', '!- Reaction reactants and products separated by =', '!- Preexponential factor, kb/h/site_den^(n-1), or ', '! sticking coefficient if adsorption reaction', '!- Beta (power to raise T in rate constant expression)', ('!- Ea (Activation Energy or Gibbs energy of activation in ' 'specified units'), ('!Adsorption reactions can be represented using the STICK ' 'keyword'), 'REACTIONS{2}{0}{2}{1}'.format(mw_str, act_unit_str, column_delimiter) ]) lines.extend(reaction_lines) lines.append('END') lines_out = '\n'.join(lines) if filename is not None: # Write the file with open(filename, 'w', newline=newline) as f_ptr: f_ptr.write(lines_out) else: return lines_out
def write_gas(nasa_species, filename=None, T=c.T0('K'), reactions=[], species_delimiter='+', reaction_delimiter='=', act_method_name='get_E_act', act_unit='kcal/mol', float_format=' .3E', stoich_format='.0f', newline='\n', column_delimiter=' ', **kwargs): """Writes the gas.inp Chemkin file. Parameters ---------- nasa_species : list of :class:`~pmutt.empirical.nasa.Nasa` objects Surface and gas species used in Chemkin mechanism. Used to write elements section filename : str, optional File name for gas.inp file. If not specified, returns gas.inp as string reactions : :class:`~pmutt.reaction.Reactions` object, optional Reactions in mechanism. Reactions with only gas-phase species will be written to this file Returns ------- lines_out : str gas.inp lines as a string if ``filename`` is None """ # Get unique elements and gas-phase species unique_elements = set() gas_species = [] for specie in nasa_species: for element in specie.elements.keys(): unique_elements.add(element) if specie.phase.upper() == 'G': gas_species.append(specie.name) unique_elements = list(unique_elements) # Get gas-phase reactions gas_reactions = [reaction for reaction in reactions if reaction.gas_phase] reaction_lines = _write_reaction_lines( reactions=gas_reactions, species_delimiter=species_delimiter, reaction_delimiter=reaction_delimiter, include_TS=False, stoich_format=stoich_format, act_method_name=act_method_name, act_unit=act_unit, float_format=float_format, column_delimiter=column_delimiter, T=T, sden_operation=None, **kwargs) # Check length of lines if any([(len(reaction_line) > 80) for reaction_line in reaction_lines]): warn_msg = ('Reaction lines exceed 80 character limit when writing ' 'gas.inp. This may cause errors when Chemkin reads this ' 'file. Consider passing a different float_format, ' 'column_delimiter, or expressing the reactions more ' 'succiently to pmutt.io.chemkin.write_gas.') warn(warn_msg, UserWarning) # Collect all the lines into a list lines = [ _get_file_timestamp(comment_char='! '), '!Elements present in gas and surface species', 'ELEMENTS' ] lines.extend(unique_elements) lines.extend(['END', '', '!Gas-phase species', 'SPECIES']) lines.extend(gas_species) lines.extend([ 'END', '', '!Gas-phase reactions. The rate constant expression is:', '!k = kb/h * (T)^beta * exp(-Ea/RT)', '!Each line has 4 columns:', '!- Reaction reactants and products separated by <=>', '!- Preexponential factor, kb/h', '!- Beta (power to raise T in rate constant expression)', ('!- Ea (Activation Energy or Gibbs energy of activation in ' 'kcal/mol'), 'REACTIONS' ]) lines.extend(reaction_lines) lines.append('END') lines_out = '\n'.join(lines) if filename is not None: with open(filename, 'w', newline=newline) as f_ptr: f_ptr.write('\n'.join(lines)) else: return lines_out
def write_EA(reactions, conditions, write_gas_phase=False, filename=None, act_method_name='get_EoRT_act', float_format=' .2E', species_delimiter='+', reaction_delimiter='<=>', stoich_format='.0f', newline='\n', column_delimiter=' '): """Writes the EAs.inp or EAg.inp file for Chemkin Parameters ---------- reactions : list of :class:`~pmutt.reaction.ChemkinReaction` objects Reactions to write conditions : list of dicts Conditions to evaluate each reaction. The key of the dictionaries should be a relevant quantity to evaluate the reaction (e.g. T, P) write_gas_phase : bool, optional If True, only gas phase reactions are written (including adsorption). If False, only surface phase reactions are written. Default is False filename : str, optional Filename for the EAs.inp file. If not specified, returns EAs file as str method_name : str, optional Method to use to calculate values. Typical values are: - 'get_EoRT_act' (default) - 'get_HoRT_act' - 'get_GoRT_act' float_format : float, optional Format to write numbers. Default is ' .2E' (scientific notation rounded to 2 decimal places with a preceding space if the value is positive) stoich_format : float, optional Format to write stoichiometric numbers. Default is '.0f' (integer) newline : str, optional Newline character to use. Default is the Linux newline character column_delimiter : str, optional Delimiter for columns. Default is ' ' Returns ------- lines_out : str EA.inp lines as a string if ``filename`` is None """ valid_reactions = [] for reaction in reactions: # Skip gas-phase reactions if we want surface phase if not write_gas_phase and reaction.gas_phase: continue # Skip surface-phase reactions if we want gas phase if write_gas_phase and not reaction.gas_phase: continue valid_reactions.append(reaction) n_reactions = len(valid_reactions) # Add initial comment help line and number of reactions lines = [ _get_file_timestamp(comment_char='! '), ('!The first line is the number of reactions. Subsequent lines follow ' 'the format'), ('!of rxn (from surf.out) followed by the EA/RT value at each run ' 'condition.'), ('!There may be one slight deviation from surf.out: any repeated ' 'species should'), ('!be included in the reaction string with a stoichiometric ' 'coefficient equal to'), ('!the number of times the species appears in the reaction. ' 'If not using'), '!MultiInput, then only the first value is used.', ' {} !Number of reactions'.format(n_reactions) ] # Find length to pad reactions max_rxn_len = _get_max_reaction_len(reactions=valid_reactions, species_delimiter=species_delimiter, reaction_delimiter=reaction_delimiter, stoich_format=stoich_format, include_TS=False) rxn_padding = max_rxn_len + len(column_delimiter) # Define string formats to use float_field = '{:%s}' % float_format str_field = '{:%d}' % rxn_padding column_line = _write_column_line(padding=rxn_padding, column_delimiter=column_delimiter, float_format=float_format, n_conditions=len(conditions)) lines.append(column_line) # Add line for each reaction step for reaction in valid_reactions: line = [ str_field.format( reaction.to_string(species_delimiter=species_delimiter, reaction_delimiter=reaction_delimiter, stoich_format=stoich_format, include_TS=False)) ] for condition in conditions: method = getattr(reaction, act_method_name) quantity = _force_pass_arguments(method, **condition) line.append(float_field.format(quantity)) lines.append(column_delimiter.join(line)) lines.append('EOF') lines_out = '\n'.join(lines) if filename is not None: with open(filename, 'w', newline=newline) as f_ptr: f_ptr.write(lines_out) else: return lines_out