def execute(self): """ Execute, in order, the jobs found in input file specified by the `input_file` attribute. """ # Initialize the logging system (both to the console and to a file in the # output directory) initialize_log(self.verbose, os.path.join(self.output_directory, 'arkane.log')) # Print some information to the beginning of the log log_header() # Load the input file for the job self.job_list = self.load_input_file(self.input_file) logging.info('') # Initialize (and clear!) the output files for the job if self.output_directory is None: self.output_directory = os.path.dirname( os.path.abspath(self.input_file)) output_file = os.path.join(self.output_directory, 'output.py') with open(output_file, 'w'): pass chemkin_file = os.path.join(self.output_directory, 'chem.inp') # write the chemkin files and run the thermo and then kinetics jobs with open(chemkin_file, 'w') as f: write_elements_section(f) f.write('SPECIES\n\n') # write each species in species block for job in self.job_list: if isinstance(job, ThermoJob): f.write(job.species.to_chemkin()) f.write('\n') f.write('\nEND\n\n\n\n') f.write('THERM ALL\n') f.write(' 300.000 1000.000 5000.000\n\n') # run thermo and statmech jobs (also writes thermo blocks to Chemkin file) supporting_info = [] hindered_rotor_info = [] bacjob_num = 1 for job in self.job_list: if isinstance(job, ThermoJob): job.execute(output_directory=self.output_directory, plot=self.plot) if isinstance(job, StatMechJob): job.execute(output_directory=self.output_directory, plot=self.plot, pdep=is_pdep(self.job_list)) if hasattr(job, 'supporting_info'): supporting_info.append(job.supporting_info) if hasattr(job, 'raw_hindered_rotor_data'): for hr_info in job.raw_hindered_rotor_data: hindered_rotor_info.append(hr_info) if isinstance(job, BACJob): job.execute(output_directory=self.output_directory, plot=self.plot, jobnum=bacjob_num) bacjob_num += 1 with open(chemkin_file, 'a') as f: f.write('\n') f.write('END\n\n\n\n') f.write('REACTIONS KCAL/MOLE MOLES\n\n') if supporting_info: # write supporting_info.csv for statmech jobs supporting_info_file = os.path.join(self.output_directory, 'supporting_information.csv') with open(supporting_info_file, 'w') as csvfile: writer = csv.writer(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) writer.writerow([ 'Label', 'Symmetry Number', 'Number of optical isomers', 'Symmetry Group', 'Rotational constant (cm-1)', 'Calculated Frequencies (unscaled and prior to projection, cm^-1)', 'Electronic energy (J/mol)', 'E0 (electronic energy + ZPE, J/mol)', 'E0 with atom and bond corrections (J/mol)', 'Atom XYZ coordinates (angstrom)', 'T1 diagnostic', 'D1 diagnostic' ]) for row in supporting_info: label = row[0] rot = '-' freq = '-' if row[4] is not None and isinstance( row[4].rotationalConstant.value, float): # diatomic species have a single rotational constant rot = '{0:.2f}'.format(row[4].rotationalConstant.value) elif row[4] is not None: rot = ', '.join([ '{0:.2f}'.format(s) for s in row[4].rotationalConstant.value ]) if row[5] is not None: freq = '' if row[6] is not None: # there is a negative frequency freq = '{0:.1f}'.format(abs(row[6])) + 'i, ' freq += ', '.join( ['{0:.1f}'.format(s) for s in row[5]]) atoms = ', '.join([ "{0} {1}".format( atom, " ".join([str(c) for c in coords])) for atom, coords in zip(row[10], row[11]) ]) writer.writerow([ label, row[1], row[2], row[3], rot, freq, row[7], row[8], row[9], atoms, row[12], row[13] ]) if hindered_rotor_info: hr_file = os.path.join(self.output_directory, 'hindered_rotor_scan_data.csv') # find longest length to set column number for energies max_energy_length = max([len(hr[4]) for hr in hindered_rotor_info]) with open(hr_file, 'w') as csvfile: writer = csv.writer(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) writer.writerow([ 'species', 'rotor_number', 'symmetry', 'resolution (degrees)', 'pivot_atoms', 'frozen_atoms' ] + [ 'energy (J/mol) {}'.format(i) for i in range(max_energy_length) ]) for row in hindered_rotor_info: writer.writerow([ row[0], row[1], row[2], row[3][1] * 180 / np.pi, row[5], row[6] ] + [a for a in row[4]]) # run kinetics and pdep jobs (also writes reaction blocks to Chemkin file) for job in self.job_list: if isinstance(job, KineticsJob): job.execute(output_directory=self.output_directory, plot=self.plot) elif isinstance(job, PressureDependenceJob) and not any( [isinstance(job, ExplorerJob) for job in self.job_list]): # if there is an explorer job the pdep job will be run in the explorer job if job.network is None: raise InputError( 'No network matched the label of the pressureDependence block and there is no explorer block ' 'to generate a network') job.execute(output_file=output_file, plot=self.plot) elif isinstance(job, ExplorerJob): thermo_library, kinetics_library, species_list = self.get_libraries( ) job.execute(output_file=output_file, plot=self.plot, species_list=species_list, thermo_library=thermo_library, kinetics_library=kinetics_library) with open(chemkin_file, 'a') as f: f.write('END\n\n') # Print some information to the end of the log log_footer() if self.save_rmg_libraries: # save RMG thermo and kinetics libraries species, reactions = list(), list() for job in self.job_list: if isinstance(job, ThermoJob) and len(job.species.molecule): species.append(job.species) elif isinstance(job, KineticsJob) \ and all([len(species.molecule) for species in job.reaction.reactants + job.reaction.products]): reactions.append(job.reaction) elif isinstance(job, PressureDependenceJob): for reaction in job.network.path_reactions: if all([ len(species.molecule) for species in reaction.reactants + reaction.products ]): reactions.append(reaction) lib_path = os.path.join(self.output_directory, 'RMG_libraries') model_chemistry = f' at the {self.model_chemistry} level of theory' if self.model_chemistry else '' lib_long_desc = f'Calculated using Arkane v{__version__}{model_chemistry}.' save_thermo_lib(species_list=species, path=lib_path, name='thermo', lib_long_desc=lib_long_desc) save_kinetics_lib(rxn_list=reactions, path=lib_path, name='kinetics', lib_long_desc=lib_long_desc)
def species(label, *args, **kwargs): """Load a species from an input file""" global species_dict, job_list if label in species_dict: raise ValueError( 'Multiple occurrences of species with label {0!r}.'.format(label)) logging.info('Loading species {0}...'.format(label)) spec = Species(label=label) species_dict[label] = spec path = None if len(args) == 1: # The argument is a path to a conformer input file path = args[0] job = StatMechJob(species=spec, path=path) logging.debug('Added species {0} to a stat mech job.'.format(label)) job_list.append(job) elif len(args) > 1: raise InputError('species {0} can only have two non-keyword argument ' 'which should be the species label and the ' 'path to a quantum file.'.format(spec.label)) if len(kwargs) > 0: # The species parameters are given explicitly structure = None E0 = None modes = [] spin_multiplicity = 0 optical_isomers = 1 molecular_weight = None collision_model = None energy_transfer_model = None thermo = None reactive = True for key, value in kwargs.items(): if key == 'structure': structure = value elif key == 'E0': E0 = value elif key == 'modes': modes = value elif key == 'spinMultiplicity': spin_multiplicity = value elif key == 'opticalIsomers': optical_isomers = value elif key == 'molecularWeight': molecular_weight = value elif key == 'collisionModel': collision_model = value elif key == 'energyTransferModel': energy_transfer_model = value elif key == 'thermo': thermo = value elif key == 'reactive': reactive = value else: raise TypeError( 'species() got an unexpected keyword argument {0!r}.'. format(key)) if structure: spec.molecule = [structure] spec.conformer = Conformer(E0=E0, modes=modes, spin_multiplicity=spin_multiplicity, optical_isomers=optical_isomers) if molecular_weight is not None: spec.molecular_weight = molecular_weight elif spec.molecular_weight is None and is_pdep(job_list): # If a structure was given, simply calling spec.molecular_weight will calculate the molecular weight # If one of the jobs is pdep and no molecular weight is given or calculated, raise an error raise ValueError( "No molecularWeight was entered for species {0}. Since a structure wasn't given" " as well, the molecularWeight, which is important for pressure dependent jobs," " cannot be reconstructed.".format(spec.label)) spec.transport_data = collision_model spec.energy_transfer_model = energy_transfer_model spec.thermo = thermo spec.reactive = reactive if spec.reactive and path is None and spec.thermo is None and spec.conformer.E0 is None: if not spec.molecule: raise InputError( 'Neither thermo, E0, species file path, nor structure specified, cannot estimate' ' thermo properties of species {0}'.format(spec.label)) try: db = get_db('thermo') if db is None: raise DatabaseError('Thermo database is None.') except DatabaseError: logging.warning( "The database isn't loaded, cannot estimate thermo for {0}. " "If it is a bath gas, set reactive = False to avoid generating " "thermo.".format(spec.label)) else: logging.info( 'No E0 or thermo found, estimating thermo and E0 of species {0} using' ' RMG-Database...'.format(spec.label)) spec.thermo = db.get_thermo_data(spec) if spec.thermo.E0 is None: th = spec.thermo.to_wilhoit() spec.conformer.E0 = th.E0 spec.thermo.E0 = th.E0 else: spec.conformer.E0 = spec.thermo.E0 if spec.reactive and spec.thermo and not spec.has_statmech( ) and structure is not None: # generate stat mech info if it wasn't provided before spec.generate_statmech() if not energy_transfer_model: # default to RMG's method of generating energy_transfer_model spec.generate_energy_transfer_model() return spec
def execute(self): """ Execute, in order, the jobs found in input file specified by the `inputFile` attribute. """ # Initialize the logging system (both to the console and to a file in the # output directory) self.initializeLog(self.verbose, os.path.join(self.outputDirectory, 'arkane.log')) # Print some information to the beginning of the log self.logHeader() # Load the input file for the job self.jobList = self.loadInputFile(self.inputFile) logging.info('') # Initialize (and clear!) the output files for the job if self.outputDirectory is None: self.outputDirectory = os.path.dirname( os.path.abspath(self.inputFile)) outputFile = os.path.join(self.outputDirectory, 'output.py') with open(outputFile, 'w') as f: pass chemkinFile = os.path.join(self.outputDirectory, 'chem.inp') # write the chemkin files and run the thermo and then kinetics jobs with open(chemkinFile, 'w') as f: writeElementsSection(f) f.write('SPECIES\n\n') # write each species in species block for job in self.jobList: if isinstance(job, ThermoJob): f.write(job.species.toChemkin()) f.write('\n') f.write('\nEND\n\n\n\n') f.write('THERM ALL\n') f.write(' 300.000 1000.000 5000.000\n\n') # run thermo and statmech jobs (also writes thermo blocks to Chemkin file) supporting_info = [] for job in self.jobList: if isinstance(job, ThermoJob): job.execute(outputFile=outputFile, plot=self.plot) if isinstance(job, StatMechJob): job.execute(outputFile=outputFile, plot=self.plot, pdep=is_pdep(self.jobList)) supporting_info.append(job.supporting_info) with open(chemkinFile, 'a') as f: f.write('\n') f.write('END\n\n\n\n') f.write('REACTIONS KCAL/MOLE MOLES\n\n') supporting_info_file = os.path.join(self.outputDirectory, 'supporting_information.csv') with open(supporting_info_file, 'wb') as csvfile: writer = csv.writer(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) writer.writerow([ 'Label', 'Rotational constant (cm-1)', 'Unscaled frequencies (cm-1)' ]) for row in supporting_info: label = row[0] rot = '-' freq = '-' if len( row ) > 1: # monoatomic species have no frequencies nor rotational constants if isinstance(row[1].rotationalConstant.value, float): # diatomic species have a single rotational constant rot = '{0:.2f}'.format(row[1].rotationalConstant.value) else: rot = ', '.join([ '{0:.2f}'.format(s) for s in row[1].rotationalConstant.value ]) freq = '' if len(row) == 4: freq = '{0:.1f}'.format(abs(row[3])) + 'i, ' freq += ', '.join(['{0:.1f}'.format(s) for s in row[2]]) writer.writerow([label, rot, freq]) # run kinetics and pdep jobs (also writes reaction blocks to Chemkin file) for job in self.jobList: if isinstance(job, KineticsJob): job.execute(outputFile=outputFile, plot=self.plot) elif isinstance(job, PressureDependenceJob) and not any( [isinstance(job, ExplorerJob) for job in self.jobList]): # if there is an explorer job the pdep job will be run in the explorer job if job.network is None: raise InputError( 'No network matched the label of the pressureDependence block and there is no explorer block ' 'to generate a network') job.execute(outputFile=outputFile, plot=self.plot) elif isinstance(job, ExplorerJob): thermoLibrary, kineticsLibrary, speciesList = self.getLibraries( ) job.execute(outputFile=outputFile, plot=self.plot, speciesList=speciesList, thermoLibrary=thermoLibrary, kineticsLibrary=kineticsLibrary) with open(chemkinFile, 'a') as f: f.write('END\n\n') # Print some information to the end of the log self.logFooter()
def execute(self): """ Execute, in order, the jobs found in input file specified by the `inputFile` attribute. """ # Initialize the logging system (both to the console and to a file in the # output directory) self.initializeLog(self.verbose, os.path.join(self.outputDirectory, 'arkane.log')) # Print some information to the beginning of the log self.logHeader() # Load the input file for the job self.jobList = self.loadInputFile(self.inputFile) logging.info('') # Initialize (and clear!) the output files for the job if self.outputDirectory is None: self.outputDirectory = os.path.dirname(os.path.abspath(self.inputFile)) outputFile = os.path.join(self.outputDirectory, 'output.py') with open(outputFile, 'w') as f: pass chemkinFile = os.path.join(self.outputDirectory, 'chem.inp') # write the chemkin files and run the thermo and then kinetics jobs with open(chemkinFile, 'w') as f: writeElementsSection(f) f.write('SPECIES\n\n') # write each species in species block for job in self.jobList: if isinstance(job,ThermoJob): f.write(job.species.toChemkin()) f.write('\n') f.write('\nEND\n\n\n\n') f.write('THERM ALL\n') f.write(' 300.000 1000.000 5000.000\n\n') # run thermo and statmech jobs (also writes thermo blocks to Chemkin file) supporting_info = [] for job in self.jobList: if isinstance(job, ThermoJob): job.execute(outputFile=outputFile, plot=self.plot) if isinstance(job, StatMechJob): job.execute(outputFile=outputFile, plot=self.plot, pdep=is_pdep(self.jobList)) supporting_info.append(job.supporting_info) with open(chemkinFile, 'a') as f: f.write('\n') f.write('END\n\n\n\n') f.write('REACTIONS KCAL/MOLE MOLES\n\n') supporting_info_file = os.path.join(self.outputDirectory, 'supporting_information.csv') with open(supporting_info_file, 'wb') as csvfile: writer = csv.writer(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) writer.writerow(['Label','Rotational constant (cm-1)','Unscaled frequencies (cm-1)']) for row in supporting_info: label = row[0] rot = '-' freq = '-' if len(row) > 1: # monoatomic species have no frequencies nor rotational constants if isinstance(row[1].rotationalConstant.value, float): # diatomic species have a single rotational constant rot = '{0:.2f}'.format(row[1].rotationalConstant.value) else: rot = ', '.join(['{0:.2f}'.format(s) for s in row[1].rotationalConstant.value]) freq = '' if len(row) == 4: freq = '{0:.1f}'.format(abs(row[3])) + 'i, ' freq += ', '.join(['{0:.1f}'.format(s) for s in row[2]]) writer.writerow([label, rot, freq]) # run kinetics and pdep jobs (also writes reaction blocks to Chemkin file) for job in self.jobList: if isinstance(job,KineticsJob): job.execute(outputFile=outputFile, plot=self.plot) elif isinstance(job, PressureDependenceJob) and not any([isinstance(job,ExplorerJob) for job in self.jobList]): #if there is an explorer job the pdep job will be run in the explorer job if job.network is None: raise InputError('No network matched the label of the pressureDependence block and there is no explorer block to generate a network') job.execute(outputFile=outputFile, plot=self.plot) elif isinstance(job, ExplorerJob): thermoLibrary,kineticsLibrary,speciesList = self.getLibraries() job.execute(outputFile=outputFile, plot=self.plot, speciesList=speciesList, thermoLibrary=thermoLibrary, kineticsLibrary=kineticsLibrary) with open(chemkinFile, 'a') as f: f.write('END\n\n') # Print some information to the end of the log self.logFooter()
def species(label, *args, **kwargs): global speciesDict, jobList if label in speciesDict: raise ValueError('Multiple occurrences of species with label {0!r}.'.format(label)) logging.info('Loading species {0}...'.format(label)) spec = Species(label=label) speciesDict[label] = spec path = None if len(args) == 1: # The argument is a path to a conformer input file path = args[0] job = StatMechJob(species=spec, path=path) logging.debug('Added species {0} to a stat mech job.'.format(label)) jobList.append(job) elif len(args) > 1: raise InputError('species {0} can only have two non-keyword argument ' 'which should be the species label and the ' 'path to a quantum file.'.format(spec.label)) if len(kwargs) > 0: # The species parameters are given explicitly structure = None E0 = None modes = [] spinMultiplicity = 0 opticalIsomers = 1 molecularWeight = None collisionModel = None energyTransferModel = None thermo = None reactive = True for key, value in kwargs.items(): if key == 'structure': structure = value elif key == 'E0': E0 = value elif key == 'modes': modes = value elif key == 'spinMultiplicity': spinMultiplicity = value elif key == 'opticalIsomers': opticalIsomers = value elif key == 'molecularWeight': molecularWeight = value elif key == 'collisionModel': collisionModel = value elif key == 'energyTransferModel': energyTransferModel = value elif key == 'thermo': thermo = value elif key == 'reactive': reactive = value else: raise TypeError('species() got an unexpected keyword argument {0!r}.'.format(key)) if structure: spec.molecule = [structure] spec.conformer = Conformer(E0=E0, modes=modes, spinMultiplicity=spinMultiplicity, opticalIsomers=opticalIsomers) if molecularWeight is not None: spec.molecularWeight = molecularWeight elif spec.molecularWeight is None and is_pdep(jobList): # If a structure was given, simply calling spec.molecularWeight will calculate the molecular weight # If one of the jobs is pdep and no molecular weight is given or calculated, raise an error raise ValueError("No molecularWeight was entered for species {0}. Since a structure wasn't given" " as well, the molecularWeight, which is important for pressure dependent jobs," " cannot be reconstructed.".format(spec.label)) spec.transportData = collisionModel spec.energyTransferModel = energyTransferModel spec.thermo = thermo spec.reactive = reactive if spec.reactive and path is None and spec.thermo is None and spec.conformer.E0 is None: if not spec.molecule: raise InputError('Neither thermo, E0, species file path, nor structure specified, cannot estimate' ' thermo properties of species {0}'.format(spec.label)) try: db = getDB('thermo') if db is None: raise DatabaseError('Thermo database is None.') except DatabaseError: logging.warn("The database isn't loaded, cannot estimate thermo for {0}. " "If it is a bath gas, set reactive = False to avoid generating thermo.".format(spec.label)) else: logging.info('No E0 or thermo found, estimating thermo and E0 of species {0} using' ' RMG-Database...'.format(spec.label)) spec.thermo = db.getThermoData(spec) if spec.thermo.E0 is None: th = spec.thermo.toWilhoit() spec.conformer.E0 = th.E0 spec.thermo.E0 = th.E0 else: spec.conformer.E0 = spec.thermo.E0 if spec.reactive and spec.thermo and not spec.hasStatMech() and structure is not None: # generate stat mech info if it wasn't provided before spec.generateStatMech() if not energyTransferModel: # default to RMG's method of generating energyTransferModel spec.generateEnergyTransferModel() return spec