diff --git a/arc/job/adapters/common.py b/arc/job/adapters/common.py index a7b91630c9..69c7c8157e 100644 --- a/arc/job/adapters/common.py +++ b/arc/job/adapters/common.py @@ -522,4 +522,15 @@ def combine_parameters(input_dict: dict, terms: list) -> Tuple[dict, List]: value = re.sub(term, '', value) input_dict_copy[key] = value + # Parameters may appear as one word, so need to split them via comma + parameters = [param.split(',') for param in parameters] + # Flatten the list of lists + parameters = [item for sublist in parameters for item in sublist] + # Remove empty spaces from the beginning and end of each parameter + parameters = [param.strip() for param in parameters] + # Parameters may appear multiple times in the input_dict, so remove duplicates + parameters = list(set(parameters)) + # Sort the list + parameters.sort() + return input_dict_copy, parameters diff --git a/arc/job/adapters/gaussian.py b/arc/job/adapters/gaussian.py index e90790bcab..23ed6a1ced 100644 --- a/arc/job/adapters/gaussian.py +++ b/arc/job/adapters/gaussian.py @@ -300,7 +300,10 @@ def write_input_file(self) -> None: input_dict['job_type_2'] = 'freq IOp(7/33=1)' elif self.job_type == 'sp': - input_dict['job_type_1'] = f'scf=(tight, direct) integral=(grid=ultrafine, {integral_algorithm})' + input_dict['job_type_1'] = f'integral=(grid=ultrafine, {integral_algorithm})' + if input_dict['trsh']: + input_dict['trsh'] += ' ' + input_dict['trsh'] += 'scf=(tight, direct)' elif self.job_type == 'scan': scans, scans_strings = list(), list() @@ -317,8 +320,11 @@ def write_input_file(self) -> None: self.torsions = torsions_to_scans(scans, direction=-1) ts = 'ts, ' if self.is_ts else '' - input_dict['job_type_1'] = f'opt=({ts}modredundant, calcfc, noeigentest, maxStep=5) scf=(tight, direct) ' \ + input_dict['job_type_1'] = f'opt=({ts}modredundant, calcfc, noeigentest, maxStep=5)' \ f'integral=(grid=ultrafine, {integral_algorithm})' + if input_dict['trsh']: + input_dict['trsh'] += ' ' + input_dict['trsh'] += 'scf=(tight, direct)' input_dict['scan'] = '\n\n' if not input_dict['scan'] else input_dict['scan'] for scan in scans_strings: input_dict['scan'] += f'D {scan} S {int(360 / self.scan_res)} {self.scan_res:.1f}\n' @@ -361,6 +367,15 @@ def write_input_file(self) -> None: input_dict['job_type_1'] += ' guess=read' if self.checkfile is not None and os.path.isfile(self.checkfile) \ else ' guess=mix' + # Fix OPT + terms_opt = [r'opt=\((.*?)\)', r'opt=(\w+)'] + input_dict, parameters_opt = combine_parameters(input_dict, terms_opt) + # If 'opt' parameters are found, concatenate and reinsert them + if parameters_opt: + # Remove duplicate parameters + combined_opt_params = ','.join(parameters_opt) + input_dict['job_type_1'] = f"opt=({combined_opt_params}) {input_dict['job_type_1']}" + #Fix SCF # This may be redundant due to additional fixes in the above code terms = ['scf=\((.*?)\)', 'scf=(\w+)'] @@ -368,6 +383,9 @@ def write_input_file(self) -> None: if parameters: input_dict['trsh'] += f" scf=({','.join(parameters)})" + # Remove double spaces + input_dict['job_type_1'] = input_dict['job_type_1'].replace(' ', ' ') + with open(os.path.join(self.local_path, input_filenames[self.job_adapter]), 'w') as f: f.write(Template(input_template).render(**input_dict)) diff --git a/arc/job/adapters/gaussian_test.py b/arc/job/adapters/gaussian_test.py index 5b81ce94a0..eda7add8a6 100644 --- a/arc/job/adapters/gaussian_test.py +++ b/arc/job/adapters/gaussian_test.py @@ -187,7 +187,7 @@ def setUpClass(cls): # Gaussian: Checkfile error and SCF error # First SCF error - qc,nosymm - job_status = {'keywords': ['SCF']} + job_status = {'keywords': ['SCF', 'NoSymm']} output_errors, ess_trsh_methods, remove_checkfile, level_of_theory, software, job_type, fine, trsh_keyword, \ memory, shift, cpu_cores, couldnt_trsh = trsh.trsh_ess_job(label, level_of_theory, server, job_status, job_type, software, fine, memory_gb, @@ -297,7 +297,7 @@ def test_write_input_file(self): %mem=14336mb %NProcShared=8 -#P opt=(calcfc) cbs-qb3 IOp(2/9=2000) IOp(1/12=5,3/44=0) +#P opt=(calcfc) cbs-qb3 IOp(2/9=2000) IOp(1/12=5,3/44=0) spc1 @@ -333,7 +333,7 @@ def test_write_input_file(self): %mem=14336mb %NProcShared=8 -#P opt=(modredundant, calcfc, noeigentest, maxStep=5) integral=(grid=ultrafine, Acc2E=12) guess=mix wb97xd/def2tzvp IOp(2/9=2000) scf=(tight, direct) +#P opt=(calcfc,maxStep=5,modredundant,noeigentest) integral=(grid=ultrafine, Acc2E=12) guess=mix wb97xd/def2tzvp IOp(2/9=2000) scf=(direct,tight) ethanol @@ -366,7 +366,7 @@ def test_write_input_file(self): %mem=14336mb %NProcShared=8 -#P uwb97xd/def2tzvp freq IOp(7/33=1) integral=(grid=ultrafine, Acc2E=12) IOp(2/9=2000) scf=(tight, direct) +#P uwb97xd/def2tzvp freq IOp(7/33=1) integral=(grid=ultrafine, Acc2E=12) IOp(2/9=2000) scf=(direct,tight) birad_singlet @@ -384,7 +384,7 @@ def test_write_input_file(self): %mem=14336mb %NProcShared=8 -#P opt=(calcfc) uwb97xd/def2tzvp IOp(2/9=2000) +#P opt=(calcfc) uwb97xd/def2tzvp IOp(2/9=2000) anion @@ -478,7 +478,7 @@ def test_trsh_write_input_file(self): %mem=14336mb %NProcShared=8 -#P opt=(calcfc, tight, maxstep=5) uwb97xd integral=(grid=ultrafine, Acc2E=14) IOp(2/9=2000) scf=(tight,direct) +#P opt=(calcfc,maxstep=5,tight) uwb97xd integral=(grid=ultrafine, Acc2E=14) IOp(2/9=2000) scf=(direct,tight) anion @@ -496,7 +496,7 @@ def test_trsh_write_input_file(self): %mem=14336mb %NProcShared=8 -#P opt=(calcfc, tight, maxstep=5) guess=mix wb97xd integral=(grid=ultrafine, Acc2E=14) IOp(2/9=2000) scf=(tight,direct) +#P opt=(calcfc,maxstep=5,tight) guess=mix wb97xd integral=(grid=ultrafine, Acc2E=14) IOp(2/9=2000) scf=(direct,tight) ethanol @@ -522,7 +522,7 @@ def test_trsh_write_input_file(self): %mem=14336mb %NProcShared=8 -#P opt=(calcfc, tight, maxstep=5) guess=mix wb97xd integral=(grid=ultrafine, Acc2E=14) IOp(2/9=2000) scf=(tight,direct,xqc,nosymm) +#P opt=(calcfc,maxstep=5,tight) guess=mix wb97xd integral=(grid=ultrafine, Acc2E=14) IOp(2/9=2000) nosymm scf=(direct,tight,xqc) ethanol @@ -548,7 +548,7 @@ def test_trsh_write_input_file(self): %mem=14336mb %NProcShared=8 -#P opt=(calcfc, tight, maxstep=5) guess=mix wb97xd integral=(grid=ultrafine, Acc2E=14) IOp(2/9=2000) scf=(tight,direct,xqc,nosymm,NDump=30) +#P opt=(calcfc,maxstep=5,tight) guess=mix wb97xd integral=(grid=ultrafine, Acc2E=14) IOp(2/9=2000) nosymm scf=(NDump=30,direct,tight,xqc) ethanol @@ -574,7 +574,7 @@ def test_trsh_write_input_file(self): %mem=14336mb %NProcShared=8 -#P opt=(calcfc, tight, maxstep=5) guess=mix wb97xd integral=(grid=ultrafine, Acc2E=14) IOp(2/9=2000) scf=(tight,direct,xqc,nosymm,NDump=30,NoDIIS) +#P opt=(calcfc,maxstep=5,tight) guess=mix wb97xd integral=(grid=ultrafine, Acc2E=14) IOp(2/9=2000) nosymm scf=(NDump=30,NoDIIS,direct,tight,xqc) ethanol @@ -600,7 +600,7 @@ def test_trsh_write_input_file(self): %mem=14336mb %NProcShared=8 -#P opt=(calcfc, tight, maxstep=5) guess=mix wb97xd integral=(grid=ultrafine, Acc2E=14) IOp(2/9=2000) opt=(cartesian,nosymm) scf=(tight,direct,xqc,nosymm,NDump=30,NoDIIS) +#P opt=(calcfc,cartesian,maxstep=5,tight) guess=mix wb97xd integral=(grid=ultrafine, Acc2E=14) IOp(2/9=2000) nosymm scf=(NDump=30,NoDIIS,direct,tight,xqc) ethanol diff --git a/arc/job/trsh.py b/arc/job/trsh.py index bfd833f5bc..3b9defc340 100644 --- a/arc/job/trsh.py +++ b/arc/job/trsh.py @@ -97,7 +97,7 @@ def determine_ess_status(output_path: str, error = 'The blank line after the coordinate section is missing, ' \ 'or charge/multiplicity was not specified correctly.' elif 'l103.exe' in line: - keywords = ['InternalCoordinateError', 'GL103'] + keywords = ['InternalCoordinateError', 'GL103','NoSymm'] error = 'Internal coordinate error' elif 'l108.exe' in line: keywords = ['InputError', 'GL108'] @@ -112,7 +112,7 @@ def determine_ess_status(output_path: str, elif 'l401.exe' in line: keywords = ['GL401'] elif 'l502.exe' in line: - keywords = ['SCF', 'GL502'] + keywords = ['SCF', 'GL502', 'NoSymm'] error = 'Unconverged SCF' elif 'l508.exe' in line: keywords = ['no_xqc', 'GL508'] @@ -867,10 +867,10 @@ def trsh_ess_job(label: str, if remove_checkfile: logger_info.append('that failed with "Basis set data is not on the checkpoint file" by removing the checkfile.') - # Check if InternalCoordinateError is in the keyword or opt=(cartesian,nosymm) + # Check if InternalCoordinateError is in the keyword or opt=(cartesian) ess_trsh_methods, trsh_keyword, couldnt_trsh = trsh_keyword_cartesian(job_status, ess_trsh_methods, job_type, trsh_keyword,couldnt_trsh) if 'cartesian' in ess_trsh_methods: - logger_info.append('using opt=cartesian with nosyym') + logger_info.append('using opt=cartesian') ess_trsh_methods, trsh_keyword, couldnt_trsh = trsh_keyword_intaccuracy(ess_trsh_methods, trsh_keyword, couldnt_trsh) if 'int=(Acc2E=14)' in ess_trsh_methods: logger_info.append('using int=(Acc2E=14)') @@ -879,11 +879,16 @@ def trsh_ess_job(label: str, ess_trsh_methods, trsh_keyword, couldnt_trsh = trsh_keyword_scf(job_status, ess_trsh_methods, trsh_keyword, couldnt_trsh) if 'scf=(NDump=30)' in ess_trsh_methods: logger_info.append('using scf=(NDump=30)') - if 'scf=(qc,nosymm)' in ess_trsh_methods: - logger_info.append('using scf=(qc,nosymm)') + if 'scf=(qc)' in ess_trsh_methods: + logger_info.append('using scf=(qc)') if 'scf=(NoDIIS)' in ess_trsh_methods: logger_info.append('using scf=(NoDIIS)') + # Check if NoSymm + ess_trsh_methods, trsh_keyword, couldnt_trsh = trsh_keyword_nosymm(job_status, ess_trsh_methods, trsh_keyword, couldnt_trsh) + if 'NoSymm' in ess_trsh_methods: + logger_info.append('using nosymm') + # Check if unconverged is in the keyword ess_trsh_methods, trsh_keyword, fine, couldnt_trsh = trsh_keyword_unconverged(job_status, ess_trsh_methods, trsh_keyword, couldnt_trsh, fine) if fine: @@ -1540,10 +1545,13 @@ def trsh_keyword_checkfile(job_status, ess_trsh_methods, couldnt_trsh) -> Tuple[ """ Check if the job requires removal of checkfile """ - if 'CheckFile' in job_status.get('keywords', '') or 'checkfile=None' in ess_trsh_methods: + if 'CheckFile' in job_status.get('keywords', '') and 'checkfile=None' not in ess_trsh_methods: ess_trsh_methods.append('checkfile=None') couldnt_trsh = False return True, ess_trsh_methods, couldnt_trsh + elif 'checkfile=None' in ess_trsh_methods: + couldnt_trsh = False + return True, ess_trsh_methods, couldnt_trsh return False, ess_trsh_methods, couldnt_trsh @@ -1551,7 +1559,11 @@ def trsh_keyword_intaccuracy(ess_trsh_methods, trsh_keyword, couldnt_trsh) -> Tu """ Check if the job requires change of 2 electron integral accuracy """ - if 'int=(Acc2E=14)' in ess_trsh_methods: + if 'int=(Acc2E=14)' not in ess_trsh_methods: + ess_trsh_methods.append('int=(Acc2E=14)') + trsh_keyword.append('int=(Acc2E=14)') + couldnt_trsh = False + elif 'int=(Acc2E=14)' not in trsh_keyword and 'int=(Acc2E=14)' in ess_trsh_methods: trsh_keyword.append('int=(Acc2E=14)') couldnt_trsh = False @@ -1563,11 +1575,12 @@ def trsh_keyword_cartesian(job_status, ess_trsh_methods, job_type, trsh_keyword: """ if 'InternalCoordinateError' in job_status['keywords'] \ and 'cartesian' not in ess_trsh_methods and job_type == 'opt': - trsh_keyword.append('opt=(cartesian,nosymm)') + ess_trsh_methods.append('cartesian') + trsh_keyword.append('opt=(cartesian)') couldnt_trsh = False - elif 'opt=(cartesian,nosymm)' in ess_trsh_methods and \ - job_type == 'opt': - trsh_keyword.append('opt=(cartesian,nosymm)') + elif 'cartesian' in ess_trsh_methods and \ + job_type == 'opt' and 'cartesian' not in trsh_keyword: + trsh_keyword.append('opt=(cartesian)') couldnt_trsh = False return ess_trsh_methods, trsh_keyword, couldnt_trsh @@ -1577,9 +1590,9 @@ def trsh_keyword_scf(job_status, ess_trsh_methods, trsh_keyword, couldnt_trsh) - Check if the job requires change of scf """ scf_pattern = r"scf=\((.*?)\)" # e.g., scf=(xqc,MaxCycle=1000), will match xqc,MaxCycle=1000 - if 'SCF' in job_status['keywords'] and 'scf=(qc,nosymm)' not in ess_trsh_methods: + if 'SCF' in job_status['keywords'] and 'scf=(qc)' not in ess_trsh_methods: # try both qc and nosymm - ess_trsh_methods.append('scf=(qc,nosymm)') + ess_trsh_methods.append('scf=(qc)') couldnt_trsh = False elif 'SCF' in job_status['keywords'] and 'scf=(NDump=30)' not in ess_trsh_methods: # Switching off Pulay's Direct Inversion @@ -1607,3 +1620,17 @@ def trsh_keyword_unconverged(job_status, ess_trsh_methods, trsh_keyword, couldnt fine = False return ess_trsh_methods, trsh_keyword, fine, couldnt_trsh + +def trsh_keyword_nosymm(job_status, ess_trsh_methods, trsh_keyword, couldnt_trsh) -> Tuple[List, List, bool]: + """ + Check if the job requires change of nosymm + """ + if 'NoSymm' in job_status['keywords'] and 'nosymm' not in ess_trsh_methods: + ess_trsh_methods.append('NoSymm') + trsh_keyword.append('nosymm') + couldnt_trsh = False + elif 'nosymm' not in trsh_keyword and any('NoSymm' in keyword for keyword in ess_trsh_methods): + trsh_keyword.append('nosymm') + couldnt_trsh = False + + return ess_trsh_methods, trsh_keyword, couldnt_trsh diff --git a/arc/job/trsh_test.py b/arc/job/trsh_test.py index 36f828c8e8..dd25ccb51b 100644 --- a/arc/job/trsh_test.py +++ b/arc/job/trsh_test.py @@ -304,14 +304,14 @@ def test_trsh_ess_job(self): self.assertFalse(couldnt_trsh) # Gaussian: test 2 - job_status = {'keywords': ['InternalCoordinateError']} + job_status = {'keywords': ['InternalCoordinateError', 'NoSymm']} output_errors, ess_trsh_methods, remove_checkfile, level_of_theory, software, job_type, fine, trsh_keyword, \ memory, shift, cpu_cores, couldnt_trsh = trsh.trsh_ess_job(label, level_of_theory, server, job_status, job_type, software, fine, memory_gb, num_heavy_atoms, cpu_cores, ess_trsh_methods) self.assertTrue(remove_checkfile) - self.assertEqual(trsh_keyword, ['opt=(cartesian,nosymm)', 'int=(Acc2E=14)'] ) + self.assertEqual(trsh_keyword, ['opt=(cartesian)', 'int=(Acc2E=14)', 'nosymm'] ) # Test Q-Chem software = 'qchem'