diff --git a/arc/scheduler.py b/arc/scheduler.py index 12f11c535b..6c59ea2545 100644 --- a/arc/scheduler.py +++ b/arc/scheduler.py @@ -79,7 +79,9 @@ class Scheduler(object): Dictionary structures:: - job_dict = {label_1: {'conformers': {0: Job1, + job_dict = {label_1: {'conf_opt': {0: Job1, + 1: Job2, ...}, + 'conf_sp': {0: Job1, 1: Job2, ...}, 'tsg': {0: Job1, 1: Job2, ...}, # TS guesses @@ -428,8 +430,8 @@ def __init__(self, if not self.job_types['opt'] and species.final_xyz is not None: # opt wasn't asked for, and it's not needed, declare it as converged self.output[species.label]['job_types']['opt'] = True - if not self.job_types['conformers'] and len(species.conformers) == 1: - # conformers weren't asked for, assign initial_xyz + if not self.job_types['conf_opt'] and len(species.conformers) == 1: + # conformers opt weren't asked for, assign initial_xyz species.initial_xyz = species.conformers[0] if species.label not in self.running_jobs: self.running_jobs[species.label if not species.multi_species else species.multi_species] = list() @@ -447,7 +449,7 @@ def __init__(self, self.run_sp_job(label=species.label) if self.job_types['onedmin']: self.run_onedmin_job(species.label) - elif species.get_xyz(generate=False) and not self.job_types['conformers'] and not self.job_types['opt'] \ + elif species.get_xyz(generate=False) and not self.job_types['conf_opt'] and not self.job_types['opt'] \ and species.irc_label is None: if self.job_types['freq']: self.run_freq_job(species.label) @@ -604,7 +606,7 @@ def schedule_jobs(self): self.run_conformer_jobs(labels=[label]) self.timer = False break - elif 'opt' in job_name: + elif 'opt' in job_name and 'conf_opt' not in job_name: # val is 'opt1', 'opt2', etc., or 'optfreq1', optfreq2', etc. job = self.job_dict[label]['opt'][job_name] if not (job.job_id in self.server_job_ids and job.job_id not in self.completed_incore_jobs): @@ -636,7 +638,7 @@ def schedule_jobs(self): self.check_freq_job(label=label, job=job) self.timer = False break - elif 'sp' in job_name: + elif 'sp' in job_name and 'conf_sp' not in job_name: job = 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): successful_server_termination = self.end_job(job=job, label=label, job_name=job_name) @@ -890,10 +892,15 @@ def run_job(self, elif conformer is not None: # Running a conformer DFT job. Append differently to job_dict. self.running_jobs[label] = list() if label not in self.running_jobs else self.running_jobs[label] - self.running_jobs[label].append(f'conformer{conformer}') # mark as a running job - if 'conformers' not in self.job_dict[label]: - self.job_dict[label]['conformers'] = dict() - self.job_dict[label]['conformers'][conformer] = job # save job object + self.running_jobs[label].append(f'{job_type}_{conformer}') # mark as a running job + if 'conf_opt' not in self.job_dict[label]: + self.job_dict[label]['conf_opt'] = dict() + if 'conf_sp' not in self.job_dict[label] and job_type == 'conf_sp': + self.job_dict[label]['conf_sp'] = dict() + if job_type == 'conf_opt': + self.job_dict[label]['conf_opt'][conformer] = job # save job object + elif job_type == 'conf_sp': + self.job_dict[label]['conf_sp'][conformer] = job # save job object elif tsg is not None: # Running a TS guess job. Append differently to job_dict. self.running_jobs[label] = list() if label not in self.running_jobs else self.running_jobs[label] @@ -1095,10 +1102,10 @@ def run_conformer_jobs(self, labels: Optional[List[str]] = None): and all([e is None for e in self.species_dict[label].conformer_energies]) \ and self.species_dict[label].number_of_atoms > 1 and not self.output[label]['paths']['geo'] \ and self.species_dict[label].yml_path is None and not self.output[label]['convergence'] \ - and (self.job_types['conformers'] and label not in self.dont_gen_confs + and (self.job_types['conf_opt'] and label not in self.dont_gen_confs or self.species_dict[label].get_xyz(generate=False) is None): # This is not a TS, opt (/composite) did not converge nor is it running, and conformer energies were - # not set. Also, either 'conformers' are set to True in job_types (and it's not in dont_gen_confs), + # not set. Also, either 'conf_opt' are set to True in job_types (and it's not in dont_gen_confs), # or they are set to False (or it's in dont_gen_confs), but the species has no 3D information. # Generate conformers. if not log_info_printed: @@ -1160,12 +1167,12 @@ def run_ts_conformer_jobs(self, label: str): ) successful_tsgs = [tsg for tsg in self.species_dict[label].ts_guesses if tsg.success] if len(successful_tsgs) > 1: - self.job_dict[label]['conformers'] = dict() + self.job_dict[label]['conf_opt'] = dict() for i, tsg in enumerate(successful_tsgs): self.run_job(label=label, xyz=tsg.initial_xyz, level_of_theory=self.ts_guess_level, - job_type='conformers', + job_type='conf_opt', conformer=i, ) tsg.conformer_index = i # Store the conformer index in the TSGuess object to match them later. @@ -1871,12 +1878,12 @@ def process_conformers(self, label): if self.species_dict[label].initial_xyz is None and self.species_dict[label].final_xyz is None \ and not self.testing: if len(self.species_dict[label].conformers) > 1: - self.job_dict[label]['conformers'] = dict() + self.job_dict[label]['conf_opt'] = dict() for i, xyz in enumerate(self.species_dict[label].conformers): self.run_job(label=label, xyz=xyz, + job_type='conf_opt', level_of_theory=self.conformer_opt_level, - job_type='conformers', conformer=i, ) elif len(self.species_dict[label].conformers) == 1: @@ -1899,7 +1906,7 @@ def process_conformers(self, label): if self.species_dict[label].charge: logger.warning(f'Isomorphism check cannot be done for charged species {label}') self.output[label]['conformers'] += 'Single conformer could not be checked for isomorphism; ' - self.output[label]['job_types']['conformers'] = True + self.output[label]['job_types']['conf_opt'] = True self.species_dict[label].conf_is_isomorphic, spawn_jobs = True, True else: logger.error(f'The only conformer for species {label} could not be checked for isomorphism ' @@ -1908,7 +1915,7 @@ def process_conformers(self, label): f'this species. To change this behaviour, pass `allow_nonisomorphic_2d = True`.') self.species_dict[label].conf_is_isomorphic, spawn_jobs = False, False if b_mol is None and (self.allow_nonisomorphic_2d or self.species_dict[label].charge): - self.output[label]['job_types']['conformers'] = True + self.output[label]['job_types']['conf_opt'] = True if b_mol is not None: try: is_isomorphic = check_isomorphism(self.species_dict[label].mol, b_mol) @@ -1924,7 +1931,7 @@ def process_conformers(self, label): logger.info(f'The only conformer for species {label} was found to be isomorphic ' f'with the 2D graph representation {b_mol.copy(deep=True).to_smiles()}\n') self.output[label]['conformers'] += 'single conformer passed isomorphism check; ' - self.output[label]['job_types']['conformers'] = True + self.output[label]['job_types']['conf_opt'] = True self.species_dict[label].conf_is_isomorphic = True else: logger.error(f'The only conformer for species {label} is not isomorphic ' @@ -2022,9 +2029,9 @@ def determine_most_stable_conformer(self, label, sp_flag=False): if self.species_dict[label].is_ts: raise SchedulerError('The determine_most_stable_conformer() method does not deal with transition ' 'state guesses.') - if 'conformers' in self.job_dict[label].keys() and all(e is None for e in self.species_dict[label].conformer_energies): + if 'conf_opt' in self.job_dict[label].keys() and all(e is None for e in self.species_dict[label].conformer_energies): logger.error(f'No conformer converged for species {label}! Trying to troubleshoot conformer jobs...') - for i, job in self.job_dict[label]['conformers'].items(): + for i, job in self.job_dict[label]['conf_opt'].items(): self.troubleshoot_ess(label, job, level_of_theory=job.level, conformer=job.conformer) else: conformer_xyz = None @@ -2032,7 +2039,7 @@ def determine_most_stable_conformer(self, label, sp_flag=False): if self.species_dict[label].conformer_energies: xyzs = self.species_dict[label].conformers else: - for job in self.job_dict[label]['conformers'].values(): + for job in self.job_dict[label]['conf_opt'].values(): xyzs.append(parser.parse_xyz_from_file(path=job.local_path_to_output_file)) xyzs_in_original_order = xyzs energies, xyzs = sort_two_lists_by_the_first(self.species_dict[label].conformer_energies, xyzs) @@ -2155,7 +2162,9 @@ def determine_most_stable_conformer(self, label, sp_flag=False): self.species_dict[label].most_stable_conformer = xyzs_in_original_order.index(conformer_xyz) logger.info(f'Conformer number {xyzs_in_original_order.index(conformer_xyz)} for species {label} is ' f'used for geometry optimization.') - self.output[label]['job_types']['conformers'] = True + self.output[label]['job_types']['conf_opt'] = True + if sp_flag: + self.output[label]['job_types']['conf_sp'] = True def determine_most_likely_ts_conformer(self, label: str): """ @@ -3059,11 +3068,11 @@ def check_all_done(self, label: str): if label in self.output and not self.output[label]['convergence']: for job_type, spawn_job_type in self.job_types.items(): if spawn_job_type and not self.output[label]['job_types'][job_type] \ - and not ((self.species_dict[label].is_ts and job_type in ['scan', 'conformers']) + and not ((self.species_dict[label].is_ts and job_type in ['scan', 'conf_opt']) or (self.species_dict[label].number_of_atoms == 1 - and job_type in ['conformers', 'opt', 'fine', 'freq', 'rotors', 'bde']) + and job_type in ['conf_opt', 'opt', 'fine', 'freq', 'rotors', 'bde']) or job_type == 'bde' and self.species_dict[label].bdes is None - or job_type == 'conformers' + or job_type == 'conf_opt' or job_type == 'irc' or job_type == 'tsg'): logger.debug(f'Species {label} did not converge.') @@ -3075,9 +3084,12 @@ def check_all_done(self, label: str): self.species_dict[label].make_ts_report() logger.info(self.species_dict[label].ts_report + '\n') zero_delta = datetime.timedelta(0) - conf_time = extremum_list([job.run_time for job in self.job_dict[label]['conformers'].values()], + conf_time = extremum_list([job.run_time for job in self.job_dict[label]['conf_opt'].values()], + return_min=False) \ + if 'conf_opt' in self.job_dict[label].keys() else zero_delta + conf_time = conf_time + extremum_list([job.run_time for job in self.job_dict[label]['conf_sp'].values()], return_min=False) \ - if 'conformers' in self.job_dict[label].keys() else zero_delta + if 'conf_sp' in self.job_dict[label].keys() else zero_delta tsg_time = extremum_list([job.run_time for job in self.job_dict[label]['tsg'].values()], return_min=False) \ if 'tsg' in self.job_dict[label].keys() else zero_delta opt_time = sum_time_delta([job.run_time for job in self.job_dict[label]['opt'].values()]) \ @@ -3086,8 +3098,8 @@ def check_all_done(self, label: str): if 'composite' in self.job_dict[label].keys() else zero_delta other_time = extremum_list([sum_time_delta([job.run_time for job in job_dictionary.values()]) for job_type, job_dictionary in self.job_dict[label].items() - if job_type not in ['conformers', 'opt', 'composite']], return_min=False) \ - if any([job_type not in ['conformers', 'opt', 'composite'] + if job_type not in ['conf_opt', 'conf_sp', 'opt', 'composite']], return_min=False) \ + if any([job_type not in ['conf_opt', 'conf_sp', 'opt', 'composite'] for job_type in self.job_dict[label].keys()]) else zero_delta self.species_dict[label].run_time = self.species_dict[label].run_time \ or (conf_time or zero_delta) + \ @@ -3135,8 +3147,10 @@ def get_completed_incore_jobs(self): if i is None: job_type = '_'.join(job_name.split('_')[:-1]) # Consider job types such as 'directed_scan'. job = self.job_dict[label][job_type][job_name] - elif 'conformer' in job_name: - job = self.job_dict[label]['conformers'][i] + elif 'conf_opt' in job_name: + job = self.job_dict[label]['conf_opt'][i] + elif 'conf_sp' in job_name: + job = self.job_dict[label]['conf_sp'][i] elif 'tsg' in job_name: job = self.job_dict[label]['tsg'][i] else: @@ -3180,12 +3194,12 @@ def troubleshoot_negative_freq(self, self.delete_all_species_jobs(label) self.species_dict[label].conformers = confs self.species_dict[label].conformer_energies = [None] * len(confs) - self.job_dict[label]['conformers'] = dict() # initialize the conformer job dictionary + self.job_dict[label]['conf_opt'] = dict() # initialize the conformer job dictionary for i, xyz in enumerate(self.species_dict[label].conformers): self.run_job(label=label, xyz=xyz, level_of_theory=self.conformer_opt_level, - job_type='conformers', + job_type='conf_opt', conformer=i, ) @@ -3512,7 +3526,7 @@ def troubleshoot_conformer_isomorphism(self, label: str): raise SchedulerError('The troubleshoot_conformer_isomorphism() method got zero conformers.') # use the first conformer of a species to determine applicable troubleshooting method - job = self.job_dict[label]['conformers'][0] + job = self.job_dict[label]['conf_opt'][0] level_of_theory = trsh_conformer_isomorphism(software=job.job_adapter, ess_trsh_methods=job.ess_trsh_methods) @@ -3533,15 +3547,15 @@ def troubleshoot_conformer_isomorphism(self, label: str): # initial xyz before troubleshooting xyz = self.species_dict[label].conformers_before_opt[conformer] - job = self.job_dict[label]['conformers'][conformer] - if 'Conformers: ' + level_of_theory not in job.ess_trsh_methods: - job.ess_trsh_methods.append('Conformers: ' + level_of_theory) + job = self.job_dict[label]['conf_opt'][conformer] + if 'conf_opt: ' + level_of_theory not in job.ess_trsh_methods: + job.ess_trsh_methods.append('conf_opt: ' + level_of_theory) self.run_job(label=label, xyz=xyz, level_of_theory=level_of_theory, job_adapter=job.job_adapter, - job_type='conformers', + job_type='conf_opt', ess_trsh_methods=job.ess_trsh_methods, conformer=conformer, ) @@ -3555,7 +3569,7 @@ def delete_all_species_jobs(self, label: str): """ logger.debug(f'Deleting all jobs for species {label}') for value in self.job_dict[label].values(): - if value in ['conformers', 'tsg']: + if value in ['conf_opt', 'tsg']: for job_name, job in self.job_dict[label][value].items(): if label in self.running_jobs.keys() and job_name in self.running_jobs[label] \ and job.execution_type != 'incore': @@ -3612,17 +3626,17 @@ def restore_running_jobs(self): if ('conformer' not in job_description or job_description['conformer'] is None) \ and ('tsg' not in job_description or job_description['tsg'] is None): self.job_dict[spc_label][job_description['job_type']] = dict() - elif 'conformers' not in self.job_dict[spc_label].keys(): - self.job_dict[spc_label]['conformers'] = dict() + elif 'conf_opt' not in self.job_dict[spc_label].keys(): + self.job_dict[spc_label]['conf_opt'] = dict() elif 'tsg' not in self.job_dict[spc_label].keys(): self.job_dict[spc_label]['tsg'] = dict() if ('conformer' not in job_description or job_description['conformer'] is None) \ and ('tsg' not in job_description or job_description['tsg'] is None): self.job_dict[spc_label][job_description['job_type']][job_description['job_name']] = job elif 'conformer' in job_description and job_description['conformer'] is not None: - if 'conformers' not in self.job_dict[spc_label].keys(): - self.job_dict[spc_label]['conformers'] = dict() - self.job_dict[spc_label]['conformers'][int(job_description['conformer'])] = job + if 'conf_opt' not in self.job_dict[spc_label].keys(): + self.job_dict[spc_label]['conf_opt'] = dict() + self.job_dict[spc_label]['conf_opt'][int(job_description['conformer'])] = job # don't generate additional conformers for this species self.dont_gen_confs.append(spc_label) elif 'tsg' in job_description and job_description['tsg'] is not None: @@ -3636,9 +3650,9 @@ def restore_running_jobs(self): content += f'\n{spc_label}: ' for job_type in self.job_dict[spc_label].keys(): for job_name in self.job_dict[spc_label][job_type].keys(): - if job_type not in ['conformers', 'tsg']: + if job_type not in ['conf_opt', 'tsg']: content += job_name + ', ' - elif job_type == 'conformers': + elif job_type == 'conf_opt': content += self.job_dict[spc_label][job_type][job_name].job_name \ + f' (conformer{job_name}), ' elif job_type == 'tsg': @@ -3662,9 +3676,11 @@ def save_restart_dict(self): self.restart_dict['running_jobs'][spc.label] = \ [self.job_dict[spc.label][job_name.rsplit('_', 1)[0]][job_name].as_dict() for job_name in self.running_jobs[spc.label] - if 'conformer' not in job_name and 'tsg' not in job_name] \ - + [self.job_dict[spc.label]['conformers'][get_i_from_job_name(job_name)].as_dict() - for job_name in self.running_jobs[spc.label] if 'conformer' in job_name] \ + if all(x not in job_name for x in ['conf_opt', 'conf_sp', 'tsg'])] \ + + [self.job_dict[spc.label]['conf_opt'][get_i_from_job_name(job_name)].as_dict() + for job_name in self.running_jobs[spc.label] if 'conf_opt' in job_name] \ + + [self.job_dict[spc.label]['conf_sp'][get_i_from_job_name(job_name)].as_dict() + for job_name in self.running_jobs[spc.label] if 'conf_sp' in job_name] \ + [self.job_dict[spc.label]['tsg'][get_i_from_job_name(job_name)].as_dict() for job_name in self.running_jobs[spc.label] if 'tsg' in job_name] logger.debug(f'Dumping restart dictionary:\n{self.restart_dict}') diff --git a/arc/scheduler_test.py b/arc/scheduler_test.py index f1e83d340d..10d5b1571e 100644 --- a/arc/scheduler_test.py +++ b/arc/scheduler_test.py @@ -54,11 +54,11 @@ def setUpClass(cls): H -1.82570782 0.42754384 -0.56130718""" cls.spc3 = ARCSpecies(label='CtripCO', smiles='C#CO', xyz=xyz3) cls.job1 = job_factory(job_adapter='gaussian', project='project_test', ess_settings=cls.ess_settings, - species=[cls.spc1], xyz=xyz1, job_type='conformers', + species=[cls.spc1], xyz=xyz1, job_type='conf_opt', conformer=0, level=Level(repr={'method': 'b97-d3', 'basis': '6-311+g(d,p)'}), project_directory=cls.project_directory, job_num=101) cls.job2 = job_factory(job_adapter='gaussian', project='project_test', ess_settings=cls.ess_settings, - species=[cls.spc1], xyz=xyz1, job_type='conformers', + species=[cls.spc1], xyz=xyz1, job_type='conf_opt', conformer=1, level=Level(repr={'method': 'b97-d3', 'basis': '6-311+g(d,p)'}), project_directory=cls.project_directory, job_num=102) cls.job3 = job_factory(job_adapter='qchem', project='project_test', ess_settings=cls.ess_settings, @@ -70,7 +70,8 @@ def setUpClass(cls): level=Level(repr={'method': 'b3lyp', 'basis': 'cbsb7'}), project_directory=cls.project_directory, job_num=104) cls.rmg_database = rmgdb.make_rmg_database_object() - cls.job_types1 = {'conformers': True, + cls.job_types1 = {'conf_opt': True, + 'conf_sp': False, 'opt': True, 'fine': False, 'freq': True, @@ -79,7 +80,8 @@ def setUpClass(cls): 'orbitals': False, 'lennard_jones': False, } - cls.job_types2 = {'conformers': True, + cls.job_types2 = {'conf_opt': True, + 'conf_sp': False, 'opt': True, 'fine': False, 'freq': True, @@ -140,9 +142,9 @@ def test_conformers(self): self.job2.local_path_to_output_file = os.path.join(ARC_PATH, 'arc', 'testing', 'methylamine_conformer_1.out') self.job2.job_status = ['done', {'status': 'done', 'keywords': list(), 'error': '', 'line': ''}] self.sched1.job_dict[label] = dict() - self.sched1.job_dict[label]['conformers'] = dict() - self.sched1.job_dict[label]['conformers'][0] = self.job1 - self.sched1.job_dict[label]['conformers'][1] = self.job2 + self.sched1.job_dict[label]['conf_opt'] = dict() + self.sched1.job_dict[label]['conf_opt'][0] = self.job1 + self.sched1.job_dict[label]['conf_opt'][1] = self.job2 self.sched1.species_dict[label].conformer_energies = [None, None] self.sched1.species_dict[label].conformers = [None, None] self.sched1.parse_conformer(job=self.job1, label=label, i=0) @@ -176,7 +178,7 @@ def test_conformers(self): 'warnings': '', 'errors': '', 'job_types': {'opt': False, 'composite': False, 'sp': False, 'fine': False, - 'freq': False, 'conformers': False}, + 'freq': False, 'conf_opt': False, 'conf_sp': False}, 'convergence': False, 'conformers': '', 'restart': ''} self.sched1.run_conformer_jobs() save_conformers_file(project_directory=self.sched1.project_directory, @@ -276,7 +278,8 @@ def test_initialize_output_dict(self): 'isomorphism': '', 'job_types': {'rotors': True, 'composite': False, - 'conformers': False, + 'conf_opt': False, + 'conf_sp': False, 'fine': False, 'freq': False, 'lennard_jones': False, @@ -644,21 +647,21 @@ def test_check_rxn_e0_by_spc(self): 'sp': os.path.join(ARC_PATH, 'arc', 'testing', 'opt', 'nC3H7.out'), 'composite': ''}, 'restart': '', 'convergence': True, - 'job_types': {'conformers': True, 'opt': True, 'freq': True, 'sp': True, 'rotors': True, 'irc': True, 'fine': True}, + 'job_types': {'conf_opt': True, 'conf_sp': False, 'opt': True, 'freq': True, 'sp': True, 'rotors': True, 'irc': True, 'fine': True}, }, 'iC3H7': {'paths': {'geo': os.path.join(ARC_PATH, 'arc', 'testing', 'opt', 'iC3H7.out'), 'freq': os.path.join(ARC_PATH, 'arc', 'testing', 'freq', 'iC3H7.out'), 'sp': os.path.join(ARC_PATH, 'arc', 'testing', 'opt', 'iC3H7.out'), 'composite': ''}, 'restart': '', 'convergence': True, - 'job_types': {'conformers': True, 'opt': True, 'freq': True, 'sp': True, 'rotors': True, 'irc': True, 'fine': True}, + 'job_types': {'conf_opt': True, 'conf_sp': False, 'opt': True, 'freq': True, 'sp': True, 'rotors': True, 'irc': True, 'fine': True}, }, 'TS0': {'paths': {'geo': os.path.join(ARC_PATH, 'arc', 'testing', 'opt', 'TS_nC3H7-iC3H7.out'), 'freq': os.path.join(ARC_PATH, 'arc', 'testing', 'freq', 'TS_nC3H7-iC3H7.out'), 'sp': os.path.join(ARC_PATH, 'arc', 'testing', 'opt', 'TS_nC3H7-iC3H7.out'), 'composite': ''}, 'restart': '', 'convergence': True, - 'job_types': {'conformers': True, 'opt': True, 'freq': True, 'sp': True, 'rotors': True, 'irc': True, 'fine': True}, + 'job_types': {'conf_opt': True, 'conf_sp': False, 'opt': True, 'freq': True, 'sp': True, 'rotors': True, 'irc': True, 'fine': True}, }, } project_directory = os.path.join(ARC_PATH, 'Projects', 'arc_project_for_testing_delete_after_usage6') @@ -717,7 +720,7 @@ def test_save_e_elect(self): opt_level=Level(method='B3LYP', basis='6-31G(d,p)', software='gaussian'), sp_level=Level(method='B3LYP', basis='6-31G(d,p)', software='gaussian'), job_types={'opt': True, 'fine_grid': False, 'freq': False, 'sp': True, 'rotors': False, - 'conformers': False, 'irc': False}, + 'conf_opt': False, 'conf_sp': False, 'irc': False}, report_e_elect=True, testing=True, )