diff --git a/arc/job/adapter.py b/arc/job/adapter.py index 428abfee1c..000c0feea3 100644 --- a/arc/job/adapter.py +++ b/arc/job/adapter.py @@ -554,6 +554,8 @@ def set_file_paths(self): self.local_path = os.path.join(self.project_directory, 'calcs', folder_name, self.species_label, self.job_name) else: self.local_path = os.path.join(self.project_directory, 'calcs', folder_name, self.species[0].multi_species, self.job_name) + self.local_path_to_input_file = os.path.join(self.local_path, settings['input_filenames'][self.job_adapter]) \ + if self.job_adapter in settings['input_filenames'] else 'input.gjf' self.local_path_to_output_file = os.path.join(self.local_path, settings['output_filenames'][self.job_adapter]) \ if self.job_adapter in settings['output_filenames'] else 'output.out' self.local_path_to_orbitals_file = os.path.join(self.local_path, 'orbitals.fchk') diff --git a/arc/main.py b/arc/main.py index b2f0e97453..87d4f0d13a 100644 --- a/arc/main.py +++ b/arc/main.py @@ -80,6 +80,8 @@ class ARC(object): instead (e.g., ``opt_level``). composite_method (str, dict, Level, optional): Composite method. conformer_level (str, dict, Level, optional): Level of theory for conformer searches. + conformer_opt_level (str, dict, Level, optional): Level of theory for conformer searches, interchangable with `conformer_level`. + conformer_sp_level (str, dict, Level, optional): Level of theory for conformer sp jobs after opt. opt_level (str, dict, Level, optional): Level of theory for geometry optimization. freq_level (str, dict, Level, optional): Level of theory for frequency calculations. sp_level (str, dict, Level, optional): Level of theory for single point calculations. @@ -167,7 +169,8 @@ class ARC(object): reactions (list): A list of :ref:`ARCReaction ` objects. level_of_theory (str): A shortcut representing either sp//geometry levels or a composite method. composite_method (Level): Composite method. - conformer_level (Level): Level of theory for conformer searches. + conformer_opt_level (Level): Level of theory for conformer searches. + conformer_sp_level (str, dict, Level, optional): Level of theory for conformer sp jobs after opt. opt_level (Level): Level of theory for geometry optimization. freq_level (Level): Level of theory for frequency calculations. sp_level (Level): Level of theory for single point calculations. @@ -245,6 +248,8 @@ def __init__(self, compute_thermo: bool = True, compute_transport: bool = False, conformer_level: Optional[Union[str, dict, Level]] = None, + conformer_opt_level: Optional[Union[str, dict, Level]] = None, + conformer_sp_level: Optional[Union[str, dict, Level]] = None, dont_gen_confs: List[str] = None, e_confs: float = 5.0, ess_settings: Dict[str, Union[str, List[str]]] = None, @@ -346,7 +351,8 @@ def __init__(self, # attributes related to level of theory specifications self.level_of_theory = level_of_theory self.composite_method = composite_method or None - self.conformer_level = conformer_level or None + self.conformer_opt_level = conformer_level or conformer_opt_level or None + self.conformer_sp_level = conformer_sp_level or None self.opt_level = opt_level or None self.freq_level = freq_level or None self.sp_level = sp_level or None @@ -489,8 +495,10 @@ def as_dict(self) -> dict: restart_dict['compute_thermo'] = self.compute_thermo if self.compute_transport: restart_dict['compute_transport'] = self.compute_transport - if self.conformer_level is not None and str(self.conformer_level).split()[0] != default_levels_of_theory['conformer']: - restart_dict['conformer_level'] = self.conformer_level.as_dict() + if self.conformer_opt_level is not None: + restart_dict['conformer_opt_level'] = self.conformer_opt_level.as_dict() + if self.conformer_sp_level is not None: + restart_dict['conformer_sp_level'] = self.conformer_sp_level.as_dict() if self.dont_gen_confs: restart_dict['dont_gen_confs'] = self.dont_gen_confs if self.ts_adapters is not None: @@ -608,7 +616,8 @@ def execute(self) -> dict: species_list=self.species, rxn_list=self.reactions, composite_method=self.composite_method, - conformer_level=self.conformer_level, + conformer_opt_level=self.conformer_opt_level, + conformer_sp_level=self.conformer_sp_level, opt_level=self.opt_level, freq_level=self.freq_level, sp_level=self.sp_level, @@ -689,7 +698,7 @@ def save_project_info_file(self): txt = '' txt += f'ARC v{self.__version__}\n' txt += f'ARC project {self.project}\n\nLevels of theory used:\n\n' - txt += f'Conformers: {self.conformer_level}\n' + txt += f'Conformers: {self.conformer_opt_level}\n' txt += f'TS guesses: {self.ts_guess_level}\n' if self.composite_method is not None: txt += f'Composite method: {self.composite_method} {fine_txt}\n' @@ -975,13 +984,17 @@ def set_levels_of_theory(self): logger.info('\n\nUsing the following levels of theory:\n') - if self.conformer_level is None: - self.conformer_level = default_levels_of_theory['conformer'] + if self.conformer_opt_level is None: + self.conformer_opt_level = default_levels_of_theory['conformer'] default_flag = ' (default)' else: default_flag = '' - self.conformer_level = Level(repr=self.conformer_level) - logger.info(f'Conformers:{default_flag} {self.conformer_level}') + self.conformer_opt_level = Level(repr=self.conformer_opt_level) + logger.info(f'Conformers opt:{default_flag} {self.conformer_opt_level}') + + if self.conformer_sp_level is not None: + self.conformer_sp_level = Level(repr=self.conformer_sp_level) + logger.info(f'Conformers sp: {self.conformer_sp_level}') if self.reactions or any([spc.is_ts for spc in self.species]): if not self.ts_guess_level: diff --git a/arc/main_test.py b/arc/main_test.py index c40adaa3a1..07b829a4b2 100644 --- a/arc/main_test.py +++ b/arc/main_test.py @@ -84,7 +84,7 @@ def test_as_dict(self): 'method': 'ccsd(t)-f12', 'method_type': 'wavefunction', 'software': 'molpro'}, - 'conformer_level': {'basis': 'def2svp', + 'conformer_opt_level': {'basis': 'def2svp', 'compatible_ess': ['gaussian', 'terachem'], 'method': 'wb97xd', 'method_type': 'dft', @@ -152,7 +152,7 @@ def test_as_dict(self): def test_from_dict(self): """Test the from_dict() method of ARC""" restart_dict = {'composite_method': '', - 'conformer_level': 'b97-d3/6-311+g(d,p)', + 'conformer_opt_level': 'b97-d3/6-311+g(d,p)', 'freq_level': 'wb97x-d3/6-311+g(d,p)', 'freq_scale_factor': 0.96, 'opt_level': 'wb97x-d3/6-311+g(d,p)', diff --git a/arc/plotter.py b/arc/plotter.py index 55044f911c..4c1fe4616f 100644 --- a/arc/plotter.py +++ b/arc/plotter.py @@ -901,6 +901,7 @@ def save_conformers_file(project_directory: str, im_freqs: Optional[List[List[float]]] = None, log_content: bool = False, before_optimization: bool = True, + conf_sp: Optional[bool] = False, ): """ Save the conformers before or after optimization. @@ -936,7 +937,10 @@ def save_conformers_file(project_directory: str, content += f'Conformers for {label}, computed using a force field:\n\n' else: level_of_theory = level_of_theory.simple() if isinstance(level_of_theory, Level) else level_of_theory - content += f'Conformers for {label}, optimized at the {level_of_theory} level:\n\n' + if not conf_sp: + content += f'Conformers for {label}, optimized at the {level_of_theory} level:\n\n' + else: + content += f'Conformers for {label}, sp at the {level_of_theory} level:\n\n' for i, xyz in enumerate(xyzs): content += f'conformer {i}:\n' if xyz is not None: diff --git a/arc/reaction_test.py b/arc/reaction_test.py index 51b87cb4a3..5c3b85e7fc 100644 --- a/arc/reaction_test.py +++ b/arc/reaction_test.py @@ -1928,7 +1928,7 @@ def test_load_ts_xyz_user_guess_from_files(self): Scheduler(project=arc_object.project, species_list=arc_object.species, rxn_list=arc_object.reactions, - conformer_level=arc_object.conformer_level, + conformer_opt_level=arc_object.conformer_opt_level, opt_level=arc_object.opt_level, freq_level=arc_object.freq_level, sp_level=arc_object.sp_level, @@ -1950,7 +1950,7 @@ def test_load_ts_xyz_user_guess_from_files(self): Scheduler(project=arc_object.project, species_list=arc_object.species, rxn_list=arc_object.reactions, - conformer_level=arc_object.conformer_level, + conformer_opt_level=arc_object.conformer_opt_level, opt_level=arc_object.opt_level, freq_level=arc_object.freq_level, sp_level=arc_object.sp_level, diff --git a/arc/scheduler.py b/arc/scheduler.py index dbaef49b1c..a5c04caa17 100644 --- a/arc/scheduler.py +++ b/arc/scheduler.py @@ -130,7 +130,8 @@ class Scheduler(object): rxn_list (list): Contains input :ref:`ARCReaction ` objects. project_directory (str): Folder path for the project: the input file path or ARC/Projects/project-name. composite_method (str, optional): A composite method to use. - conformer_level (Union[str, dict], optional): The level of theory to use for conformer comparisons. + conformer_opt_level (Union[str, dict], optional): The level of theory to use for conformer comparisons. + conformer_sp_level (Union[str, dict], optional): The level of theory to use for conformer sp jobs. opt_level (Union[str, dict], optional): The level of theory to use for geometry optimizations. freq_level (Union[str, dict], optional): The level of theory to use for frequency calculations. sp_level (Union[str, dict], optional): The level of theory to use for single point energy calculations. @@ -202,7 +203,8 @@ class Scheduler(object): bath_gas (str): A bath gas. Currently used in OneDMin to calc L-J parameters. Allowed values are He, Ne, Ar, Kr, H2, N2, O2. composite_method (str): A composite method to use. - conformer_level (dict): The level of theory to use for conformer comparisons. + conformer_opt_level (dict): The level of theory to use for conformer comparisons. + conformer_sp_level (dict): The level of theory to use for conformer sp jobs. opt_level (dict): The level of theory to use for geometry optimizations. freq_level (dict): The level of theory to use for frequency calculations. sp_level (dict): The level of theory to use for single point energy calculations. @@ -230,7 +232,8 @@ def __init__(self, species_list: list, project_directory: str, composite_method: Optional[Level] = None, - conformer_level: Optional[Level] = None, + conformer_opt_level: Optional[Level] = None, + conformer_sp_level: Optional[Level] = None, opt_level: Optional[Level] = None, freq_level: Optional[Level] = None, sp_level: Optional[Level] = None, @@ -310,7 +313,8 @@ def __init__(self, self.report_time = time.time() # init time for reporting status every 1 hr self.servers = list() self.composite_method = composite_method - self.conformer_level = conformer_level + self.conformer_opt_level = conformer_opt_level + self.conformer_sp_level = conformer_sp_level self.ts_guess_level = ts_guess_level self.opt_level = opt_level self.freq_level = freq_level @@ -528,6 +532,7 @@ def schedule_jobs(self): self.run_opt_job(species.label, fine=self.fine_only) self.run_conformer_jobs() self.spawn_ts_jobs() # If all reactants/products are already known (Arkane yml or restart), spawn TS searches. + conformer_jobs_done = False while self.running_jobs != {}: self.timer = True for label in self.unique_species_labels: @@ -543,24 +548,37 @@ def schedule_jobs(self): continue job_list = self.running_jobs[label] for job_name in job_list: - if 'conformer' in job_name: - i = get_i_from_job_name(job_name) - job = self.job_dict[label]['conformers'][i] + if ('conformer' in job_name or 'sp' in job_name) and not conformer_jobs_done: + i = get_i_from_job_name(job_name) if 'conformer' in job_name else None + job = self.job_dict[label]['conformers'][i] if 'conformer' in job_name else self.job_dict[label]['sp'][job_name] if not (job.job_id in self.server_job_ids and job.job_id not in self.completed_incore_jobs): # this is a completed conformer job successful_server_termination = self.end_job(job=job, label=label, job_name=job_name) if successful_server_termination: + if i is None: + xyz = parser.parse_xyz_from_file(path=job.local_path_to_input_file) + for index, conformer in enumerate(self.species_dict[label].conformers): + if conformer == xyz: + i = index + break troubleshooting_conformer = self.parse_conformer(job=job, label=label, i=i) + if self.conformer_sp_level is not None and 'conformer' in job_name: + self.run_job(label=label, + xyz=self.species_dict[label].conformers[i], + level_of_theory=self.conformer_sp_level, + job_type='sp', + ) if troubleshooting_conformer: break # Just terminated a conformer job. # Are there additional conformer jobs currently running for this species? for spec_jobs in job_list: - if 'conformer' in spec_jobs and spec_jobs != job_name: + if 'conformer' or 'sp' in spec_jobs and spec_jobs != job_name: break else: # All conformer jobs terminated. # Check isomorphism and run opt on most stable conformer geometry. + conformer_jobs_done = True logger.info(f'\nConformer jobs for {label} successfully terminated.\n') if self.species_dict[label].is_ts: self.determine_most_likely_ts_conformer(label) @@ -1100,11 +1118,11 @@ def run_conformer_jobs(self, labels: Optional[List[str]] = None): else: # Run the combinatorial method w/o fitting a force field. n_confs = self.n_confs if self.species_dict[label].multi_species is None else 1 - self.species_dict[label].generate_conformers( - n_confs=n_confs, - e_confs=self.e_confs, - plot_path=os.path.join(self.project_directory, 'output', 'Species', - label, 'geometry', 'conformers')) + self.species_dict[label].generate_conformers(n_confs=n_confs, + e_confs=self.e_confs, + plot_path=os.path.join(self.project_directory, 'output', 'Species', + label, 'geometry', 'conformers'), + ) self.process_conformers(label) # TSs: elif self.species_dict[label].is_ts \ @@ -1840,8 +1858,8 @@ def process_directed_scans(self, label: str, pivots: Union[List[int], List[List[ def process_conformers(self, label): """ - Process the generated conformers and spawn DFT jobs at the conformer_level. - If more than one conformer is available, they will be optimized at the DFT conformer_level. + Process the generated conformers and spawn DFT jobs at the conformer_opt_level. + If more than one conformer is available, they will be optimized at the DFT conformer_opt_level. Args: label (str): The species label. @@ -1849,7 +1867,7 @@ def process_conformers(self, label): plotter.save_conformers_file(project_directory=self.project_directory, label=label, xyzs=self.species_dict[label].conformers, - level_of_theory=self.conformer_level, + level_of_theory=self.conformer_opt_level, multiplicity=self.species_dict[label].multiplicity, charge=self.species_dict[label].charge, is_ts=False, @@ -1864,7 +1882,7 @@ def process_conformers(self, label): for i, xyz in enumerate(self.species_dict[label].conformers): self.run_job(label=label, xyz=xyz, - level_of_theory=self.conformer_level, + level_of_theory=self.conformer_opt_level, job_type='conformers', conformer=i, ) @@ -2027,12 +2045,13 @@ def determine_most_stable_conformer(self, label): plotter.save_conformers_file(project_directory=self.project_directory, label=label, xyzs=self.species_dict[label].conformers, - level_of_theory=self.conformer_level, + level_of_theory=self.conformer_opt_level if self.conformer_sp_level is None else self.conformer_sp_level, multiplicity=self.species_dict[label].multiplicity, charge=self.species_dict[label].charge, is_ts=False, energies=self.species_dict[label].conformer_energies, before_optimization=False, + conf_sp=False if self.conformer_sp_level is None else True, ) # after optimization # Run isomorphism checks if a 2D representation is available if self.species_dict[label].mol is not None: @@ -2088,7 +2107,7 @@ def determine_most_stable_conformer(self, label): conformer_xyz = xyz if 'Conformers optimized and compared' not in self.output[label]['conformers']: self.output[label]['conformers'] += \ - f'Conformers optimized and compared at {self.conformer_level.simple()}; ' + f'Conformers optimized and compared at {self.conformer_opt_level.simple()}; ' break else: if i == 0: @@ -2127,11 +2146,11 @@ def determine_most_stable_conformer(self, label): else: # troubleshoot when all conformers of a species failed isomorphic test logger.warning(f'Isomorphism check for all conformers of species {label} failed at ' - f'{self.conformer_level.simple()}. ' + f'{self.conformer_opt_level.simple()}. ' f'Attempting to troubleshoot using a different level.') self.output[label]['conformers'] += \ f'Error: No conformer was found to be isomorphic with the 2D graph representation at ' \ - f'{self.conformer_level.simple()}; ' + f'{self.conformer_opt_level.simple()}; ' self.troubleshoot_conformer_isomorphism(label=label) else: logger.warning(f'Could not run isomorphism check for species {label} due to missing 2D graph ' @@ -3171,7 +3190,7 @@ def troubleshoot_negative_freq(self, for i, xyz in enumerate(self.species_dict[label].conformers): self.run_job(label=label, xyz=xyz, - level_of_theory=self.conformer_level, + level_of_theory=self.conformer_opt_level, job_type='conformers', conformer=i, ) diff --git a/arc/scheduler_test.py b/arc/scheduler_test.py index 7e08bba031..f1e83d340d 100644 --- a/arc/scheduler_test.py +++ b/arc/scheduler_test.py @@ -89,7 +89,7 @@ def setUpClass(cls): cls.sched1 = Scheduler(project='project_test_1', ess_settings=cls.ess_settings, species_list=[cls.spc1, cls.spc2, cls.spc3], composite_method=None, - conformer_level=Level(repr=default_levels_of_theory['conformer']), + conformer_opt_level=Level(repr=default_levels_of_theory['conformer']), opt_level=Level(repr=default_levels_of_theory['opt']), freq_level=Level(repr=default_levels_of_theory['freq']), sp_level=Level(repr=default_levels_of_theory['sp']), @@ -105,7 +105,7 @@ def setUpClass(cls): cls.sched2 = Scheduler(project='project_test_2', ess_settings=cls.ess_settings, species_list=[cls.spc1, cls.spc2, cls.spc3], composite_method=None, - conformer_level=Level(repr=default_levels_of_theory['conformer']), + conformer_opt_level=Level(repr=default_levels_of_theory['conformer']), opt_level=Level(repr=default_levels_of_theory['opt']), freq_level=Level(repr=default_levels_of_theory['freq']), sp_level=Level(repr=default_levels_of_theory['sp']), @@ -121,7 +121,7 @@ def setUpClass(cls): cls.sched3 = Scheduler(project='project_test_4', ess_settings=cls.ess_settings, species_list=[cls.spc1], composite_method=Level(repr='CBS-QB3'), - conformer_level=Level(repr=default_levels_of_theory['conformer']), + conformer_opt_level=Level(repr=default_levels_of_theory['conformer']), opt_level=Level(repr=default_levels_of_theory['freq_for_composite']), freq_level=Level(repr=default_levels_of_theory['freq_for_composite']), scan_level=Level(repr=default_levels_of_theory['scan_for_composite']), @@ -221,7 +221,7 @@ def test_determine_adaptive_level(self): ess_settings=self.ess_settings, species_list=[self.spc1, self.spc2], composite_method=None, - conformer_level=default_levels_of_theory['conformer'], + conformer_opt_level=default_levels_of_theory['conformer'], opt_level=default_levels_of_theory['opt'], freq_level=default_levels_of_theory['freq'], sp_level=default_levels_of_theory['sp'], diff --git a/arc/testing/restart/1_restart_thermo/restart.yml b/arc/testing/restart/1_restart_thermo/restart.yml index 1cad62fe28..48807079f5 100644 --- a/arc/testing/restart/1_restart_thermo/restart.yml +++ b/arc/testing/restart/1_restart_thermo/restart.yml @@ -9,7 +9,7 @@ arkane_level_of_theory: software: gaussian calc_freq_factor: true compute_transport: false -conformer_level: +conformer_opt_level: basis: 6-31g(d,p) dispersion: empiricaldispersion=gd3bj method: b3lyp diff --git a/arc/testing/restart/2_restart_rate/restart.yml b/arc/testing/restart/2_restart_rate/restart.yml index b80e763cad..08b1d918f3 100644 --- a/arc/testing/restart/2_restart_rate/restart.yml +++ b/arc/testing/restart/2_restart_rate/restart.yml @@ -3,7 +3,7 @@ calc_freq_factor: false compare_to_rmg: false composite_method: '' bac_type: null -conformer_level: +conformer_opt_level: auxiliary_basis: '' basis: def2svp dispersion: '' diff --git a/arc/testing/restart/3_restart_bde/restart.yml b/arc/testing/restart/3_restart_bde/restart.yml index 0040081fcb..97f37c09fc 100644 --- a/arc/testing/restart/3_restart_bde/restart.yml +++ b/arc/testing/restart/3_restart_bde/restart.yml @@ -9,7 +9,7 @@ arkane_level_of_theory: software: gaussian compare_to_rmg: false compute_transport: false -conformer_level: +conformer_opt_level: basis: def2svp compatible_ess: - gaussian diff --git a/arc/testing/restart/5_TS1/restart.yml b/arc/testing/restart/5_TS1/restart.yml index 7478bf7e8f..2dcd0b4d06 100644 --- a/arc/testing/restart/5_TS1/restart.yml +++ b/arc/testing/restart/5_TS1/restart.yml @@ -4,7 +4,7 @@ arkane_level_of_theory: method: ccsd(t)-f12 method_type: wavefunction software: molpro -conformer_level: +conformer_opt_level: basis: def2tzvp compatible_ess: - gaussian