def test_rotor_symmetry(self): """Test that ARC automatically determines a correct rotor symmetry""" path1 = os.path.join(arc_path, 'arc', 'testing', 'rotor_scans', 'OOC1CCOCO1.out') # symmetry = 1; min at -10 o path2 = os.path.join(arc_path, 'arc', 'testing', 'rotor_scans', 'H2O2.out') # symmetry = 1 path3 = os.path.join(arc_path, 'arc', 'testing', 'rotor_scans', 'N2O3.out') # symmetry = 2 path4 = os.path.join(arc_path, 'arc', 'testing', 'rotor_scans', 'sBuOH.out') # symmetry = 3 path5 = os.path.join(arc_path, 'arc', 'testing', 'rotor_scans', 'CH3C(O)O_FreeRotor.out') # symmetry = 6 symmetry1, _ = determine_rotor_symmetry(rotor_path=path1, label='label', pivots=[3, 4]) symmetry2, _ = determine_rotor_symmetry(rotor_path=path2, label='label', pivots=[3, 4]) symmetry3, _ = determine_rotor_symmetry(rotor_path=path3, label='label', pivots=[3, 4]) symmetry4, _ = determine_rotor_symmetry(rotor_path=path4, label='label', pivots=[3, 4]) symmetry5, _ = determine_rotor_symmetry(rotor_path=path5, label='label', pivots=[3, 4]) self.assertEqual(symmetry1, 1) self.assertEqual(symmetry2, 1) self.assertEqual(symmetry3, 2) self.assertEqual(symmetry4, 3) self.assertEqual(symmetry5, 6)
def check_scan_quality(spc): """ A helper function used to get the status of rotor scans """ for rotor in spc['rotors_dict'].values(): path = rotor['scan_path'] if path: scan_args = parse_scan_args(path) energies, _ = parse_1d_scan_energies(path) invalid, reason, _, actions = scan_quality_check( spc['label'], pivots=rotor['scan'][1:-1], energies=energies, scan_res=scan_args['step_size'], log_file=path) else: rotor['success'] = False rotor['invalidation_reason'] = 'Unknown' continue if not invalid: rotor['success'] = True rotor['symmetry'] = determine_rotor_symmetry( label=spc['label'], pivots=rotor['scan'][1:3], energies=energies)[0] continue if 'change conformer' in actions: print(spc['label'] + ': has a bad conformer orientation according to ' + str(rotor['scan'])) xyz = xyz_to_xyz_file_format(actions['change conformer']) return {'label': spc['label'], 'change conformer': xyz} if 'barrier' in reason: rotor['success'] = False rotor['invalidation_reason'] = reason continue # Otherwise need to come up with troubleshooting methods species_scan_lists = [rotor['scan']] scan_trsh, scan_res = trsh_scan_job(spc['label'], scan_args['step_size'], rotor['scan'], species_scan_lists, actions, path) rotor['trsh_methods'] = [{ 'scan_trsh': scan_trsh, 'scan_res': scan_res }] rotor['archived'].append(rotor['scan_path']) spc['scan'].remove(rotor['scan_path']) return spc
def _generate_arkane_species_file(self, species): """ A helper function for generating the Arkane species file. Assigns the input file path to species.arkane_file. Args: species (ARCSpecies): The species to process. Returns: str: The Arkane output path. """ folder_name = 'rxns' if species.is_ts else 'Species' output_path = os.path.join(self.project_directory, 'output', folder_name, species.label, 'arkane') if not os.path.isdir(output_path): os.makedirs(output_path) if species.yml_path is not None: species.arkane_file = species.yml_path return output_path species.determine_symmetry() try: sp_path = self.output[species.label]['composite'] except KeyError: try: sp_path = self.output[species.label]['sp'] except KeyError: raise SchedulerError('Could not find path to sp calculation for species {0}'.format( species.label)) if species.number_of_atoms == 1: freq_path = sp_path opt_path = sp_path else: freq_path = self.output[species.label]['freq'] opt_path = self.output[species.label]['freq'] rotors, rotors_description = '', '' if any([i_r_dict['success'] for i_r_dict in species.rotors_dict.values()]): rotors = '\n\nrotors = [' rotors_description = '1D rotors:\n' for i in range(species.number_of_rotors): pivots = str(species.rotors_dict[i]['pivots']) scan = str(species.rotors_dict[i]['scan']) if species.rotors_dict[i]['success']: rotor_path = species.rotors_dict[i]['scan_path'] rotor_type = determine_rotor_type(rotor_path) top = str(species.rotors_dict[i]['top']) try: rotor_symmetry, max_e = determine_rotor_symmetry(rotor_path, species.label, pivots) except RotorError: logger.error('Could not determine rotor symmetry for species {0} between pivots {1}.' ' Setting the rotor symmetry to 1, which is probably WRONG.'.format( species.label, pivots)) rotor_symmetry = 1 max_e = None max_e = ', max scan energy: {0:.2f} kJ/mol'.format(max_e) if max_e is not None else '' free = ' (set as a FreeRotor)' if rotor_type == 'FreeRotor' else '' rotors_description += 'pivots: ' + str(pivots) + ', dihedral: ' + str(scan) +\ ', rotor symmetry: ' + str(rotor_symmetry) + max_e + free + '\n' if rotor_type == 'HinderedRotor': rotors += input_files['arkane_hindered_rotor'].format(rotor_path=rotor_path, pivots=pivots, top=top, symmetry=rotor_symmetry) elif rotor_type == 'FreeRotor': rotors += input_files['arkane_free_rotor'].format(rotor_path=rotor_path, pivots=pivots, top=top, symmetry=rotor_symmetry) if i < species.number_of_rotors - 1: rotors += ',\n ' else: rotors_description += '* Invalidated! pivots: ' + str(pivots) + ', dihedral: ' + str(scan) +\ ', invalidation reason: ' + species.rotors_dict[i]['invalidation_reason'] +\ '\n' rotors += ']' species.long_thermo_description += rotors_description + '\n' # write the Arkane species input file input_file_path = os.path.join(self.project_directory, 'output', folder_name, species.label, '{0}_arkane_input.py'.format(species.label)) input_file = input_files['arkane_input_species'] if self.use_bac and not species.is_ts: logger.info('Using the following BAC for {0}: {1}'.format(species.label, species.bond_corrections)) bonds = 'bonds = {0}\n\n'.format(species.bond_corrections) else: logger.debug('NOT using BAC for {0}'.format(species.label)) bonds = '' input_file = input_file.format(bonds=bonds, symmetry=species.external_symmetry, multiplicity=species.multiplicity, optical=species.optical_isomers, sp_level=self.sp_level, sp_path=sp_path, opt_path=opt_path, freq_path=freq_path, rotors=rotors) with open(input_file_path, 'wb') as f: f.write(input_file) species.arkane_file = input_file_path return output_path
def generate_arkane_species_file(self, species: Type[ARCSpecies], bac_type: Optional[str], ) -> Optional[str]: """ A helper function for generating an Arkane Python species file. Assigns the path of the generated file to the species.arkane_file attribute. Args: species (ARCSpecies): The species to process. bac_type (str): The bond additivity correction type. 'p' for Petersson- or 'm' for Melius-type BAC. ``None`` to not use BAC. Returns: str: The path to the species arkane folder (Arkane's default output folder). """ folder_name = 'rxns' if species.is_ts else 'Species' species_folder_path = os.path.join(self.output_directory, folder_name, species.label) arkane_output_path = os.path.join(species_folder_path, 'arkane') if not os.path.isdir(arkane_output_path): os.makedirs(arkane_output_path) if species.yml_path is not None: species.arkane_file = species.yml_path return arkane_output_path species.determine_symmetry() sp_path = self.output_dict[species.label]['paths']['composite'] \ or self.output_dict[species.label]['paths']['sp'] if species.number_of_atoms == 1: freq_path = sp_path opt_path = sp_path else: freq_path = self.output_dict[species.label]['paths']['freq'] opt_path = self.output_dict[species.label]['paths']['freq'] return_none_text = None if not sp_path: return_none_text = 'path to the sp calculation' if not freq_path: return_none_text = 'path to the freq calculation' if not os.path.isfile(freq_path): return_none_text = f'the freq file in path {freq_path}' if not os.path.isfile(sp_path): return_none_text = f'the freq file in path {sp_path}' if return_none_text is not None: logger.error(f'Could not find {return_none_text} for species {species.label}. Not calculating properties.') return None rotors, rotors_description = '', '' if species.rotors_dict is not None and any([i_r_dict['pivots'] for i_r_dict in species.rotors_dict.values()]): rotors = '\n\nrotors = [' rotors_description = '1D rotors:\n' for i in range(species.number_of_rotors): pivots = str(species.rotors_dict[i]['pivots']) scan = str(species.rotors_dict[i]['scan']) if species.rotors_dict[i]['success']: rotor_path = species.rotors_dict[i]['scan_path'] rotor_type = determine_rotor_type(rotor_path) top = str(species.rotors_dict[i]['top']) try: rotor_symmetry, max_e, _ = determine_rotor_symmetry(species.label, pivots, rotor_path) except RotorError: logger.error(f'Could not determine rotor symmetry for species {species.label} between ' f'pivots {pivots}. Setting the rotor symmetry to 1, ' f'this could very well be WRONG.') rotor_symmetry = 1 max_e = None scan_trsh = '' if 'trsh_methods' in species.rotors_dict[i]: scan_res = 360 for scan_trsh_method in species.rotors_dict[i]['trsh_methods']: if 'scan_trsh' in scan_trsh_method and len(scan_trsh) < len(scan_trsh_method['scan_trsh']): scan_trsh = scan_trsh_method['scan_trsh'] if 'scan_res' in scan_trsh_method and scan_res > scan_trsh_method['scan_res']: scan_res = scan_trsh_method['scan_res'] scan_trsh = f'Troubleshot with the following constraints and {scan_res} degrees ' \ f'resolution:\n{scan_trsh}' if scan_trsh else '' max_e = f', max scan energy: {max_e:.2f} kJ/mol' if max_e is not None else '' free = ' (set as a FreeRotor)' if rotor_type == 'FreeRotor' else '' rotors_description += f'pivots: {pivots}, dihedral: {scan}, ' \ f'rotor symmetry: {rotor_symmetry}{max_e}{free}\n{scan_trsh}' if rotor_type == 'HinderedRotor': rotors += input_files['arkane_hindered_rotor'].format(rotor_path=rotor_path, pivots=pivots, top=top, symmetry=rotor_symmetry) elif rotor_type == 'FreeRotor': rotors += input_files['arkane_free_rotor'].format(rotor_path=rotor_path, pivots=pivots, top=top, symmetry=rotor_symmetry) if i < species.number_of_rotors - 1: rotors += ',\n ' else: rotors_description += f'* Invalidated! pivots: {pivots}, dihedral: {scan}, ' \ f'invalidation reason: {species.rotors_dict[i]["invalidation_reason"]}\n' rotors += ']' if 'rotors' not in species.long_thermo_description: species.long_thermo_description += rotors_description + '\n' # write the Arkane species input file bac_txt = '' if bac_type is not None else '_no_BAC' input_file_path = os.path.join(species_folder_path, f'{species.label}_arkane_input{bac_txt}.py') input_file = input_files['arkane_input_species'] if 'sp_sol' not in self.output_dict[species.label]['paths'] \ else input_files['arkane_input_species_explicit_e'] if bac_type is not None and not species.is_ts: logger.info(f'Using the following BAC (type {bac_type}) for {species.label}: {species.bond_corrections}') bonds = f'bonds = {species.bond_corrections}\n\n' else: logger.debug(f'NOT using BAC for {species.label}') bonds = '' if 'sp_sol' not in self.output_dict[species.label]['paths']: input_file = input_file.format(bonds=bonds, symmetry=species.external_symmetry, multiplicity=species.multiplicity, optical=species.optical_isomers, sp_path=sp_path, opt_path=opt_path, freq_path=freq_path, rotors=rotors) else: # e_elect = e_original + sp_e_sol_corrected - sp_e_uncorrected original_log = ess_factory(self.output_dict[species.label]['paths']['sp']) e_original = original_log.load_energy() e_sol_log = ess_factory(self.output_dict[species.label]['paths']['sp_sol']) e_sol = e_sol_log.load_energy() e_no_sol_log = ess_factory(self.output_dict[species.label]['paths']['sp_no_sol']) e_no_sol = e_no_sol_log.load_energy() e_elect = (e_original + e_sol - e_no_sol) / (constants.E_h * constants.Na) # convert J/mol to Hartree logger.info(f'\nSolvation correction scheme for {species.label}:\n' f'Original electronic energy: {e_original * 0.001} kJ/mol\n' f'Solvation correction: {(e_sol - e_no_sol) * 0.001} kJ/mol\n' f'New electronic energy: {(e_original + e_sol - e_no_sol) * 0.001} kJ/mol\n\n') print(f'e_elect final: {(e_original + e_sol - e_no_sol) * 0.001} kJ/mol\n\n') input_file = input_files['arkane_input_species_explicit_e'] input_file = input_file.format(bonds=bonds, symmetry=species.external_symmetry, multiplicity=species.multiplicity, optical=species.optical_isomers, sp_level=self.sp_level, e_elect=e_elect, opt_path=opt_path, freq_path=freq_path, rotors=rotors) if freq_path: with open(input_file_path, 'w') as f: f.write(input_file) species.arkane_file = input_file_path else: species.arkane_file = None return arkane_output_path