From a69af519aaa16bdc3f2918af94ad5711d7bee529 Mon Sep 17 00:00:00 2001 From: Jintao Date: Sun, 26 May 2024 08:09:14 +0800 Subject: [PATCH 1/5] Define new variable `before_optimization` for function `save_conformer` --- arc/plotter.py | 2 ++ arc/scheduler.py | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/arc/plotter.py b/arc/plotter.py index 8ec8063adf..5bbf5194ae 100644 --- a/arc/plotter.py +++ b/arc/plotter.py @@ -900,6 +900,7 @@ def save_conformers_file(project_directory: str, ts_methods: Optional[List[str]] = None, im_freqs: Optional[List[List[float]]] = None, log_content: bool = False, + before_optimization: bool = True, ): """ Save the conformers before or after optimization. @@ -918,6 +919,7 @@ def save_conformers_file(project_directory: str, ts_methods (list, optional): Entries are method names used to generate the TS guess. im_freqs (list, optional): Entries lists of imaginary frequencies. log_content (bool): Whether to log the content of the conformers file. ``True`` to log, default is ``False``. + before_optimization (bool): Whether the conformers are before DFT optimization. ``True`` for before, default is ``True``. """ spc_dir = 'rxns' if is_ts else 'Species' geo_dir = os.path.join(project_directory, 'output', spc_dir, label, 'geometry', 'conformers') diff --git a/arc/scheduler.py b/arc/scheduler.py index ee9514fe2e..b6674db9e8 100644 --- a/arc/scheduler.py +++ b/arc/scheduler.py @@ -1145,6 +1145,7 @@ def run_ts_conformer_jobs(self, label: str): f'{tsg.method_direction if tsg.method_direction is not None else ""} ' f'{tsg.method_index if tsg.method_index is not None else ""} ' for tsg in self.species_dict[label].ts_guesses], + before_optimization=True, ) successful_tsgs = [tsg for tsg in self.species_dict[label].ts_guesses if tsg.success] if len(successful_tsgs) > 1: @@ -1852,6 +1853,7 @@ def process_conformers(self, label): multiplicity=self.species_dict[label].multiplicity, charge=self.species_dict[label].charge, is_ts=False, + before_optimization=True, ) # before optimization self.species_dict[label].conformers_before_opt = tuple(self.species_dict[label].conformers) if self.species_dict[label].initial_xyz is None and self.species_dict[label].final_xyz is None \ @@ -2028,8 +2030,9 @@ def determine_most_stable_conformer(self, label): multiplicity=self.species_dict[label].multiplicity, charge=self.species_dict[label].charge, is_ts=False, - energies=self.species_dict[label].conformer_energies, # after optimization - ) + energies=self.species_dict[label].conformer_energies, + before_optimization=False, + ) # after optimization # Run isomorphism checks if a 2D representation is available if self.species_dict[label].mol is not None: for i, xyz in enumerate(xyzs): @@ -2244,6 +2247,7 @@ def determine_most_likely_ts_conformer(self, label: str): for tsg in self.species_dict[label].ts_guesses], im_freqs=[tsg.imaginary_freqs for tsg in self.species_dict[label].ts_guesses] if any(tsg.imaginary_freqs is not None for tsg in self.species_dict[label].ts_guesses) else None, + before_optimization=False, ) def parse_composite_geo(self, @@ -2579,6 +2583,7 @@ def check_negative_freq(self, for tsg in self.species_dict[label].ts_guesses], im_freqs=[tsg.imaginary_freqs for tsg in self.species_dict[label].ts_guesses] if any(tsg.imaginary_freqs is not None for tsg in self.species_dict[label].ts_guesses) else None, + before_optimization=False, ) if not self.testing: # Update restart dictionary and save the yaml restart file: From b62a5e06bc863ee0e33ec7eebd3c685912d57219 Mon Sep 17 00:00:00 2001 From: Jintao Date: Sun, 26 May 2024 08:10:45 +0800 Subject: [PATCH 2/5] Simplify the logic of function `save_conformer` Now it decides the condition based on the new var `before_optimization` --- arc/plotter.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/arc/plotter.py b/arc/plotter.py index 5bbf5194ae..55044f911c 100644 --- a/arc/plotter.py +++ b/arc/plotter.py @@ -925,16 +925,16 @@ def save_conformers_file(project_directory: str, geo_dir = os.path.join(project_directory, 'output', spc_dir, label, 'geometry', 'conformers') if not os.path.exists(geo_dir): os.makedirs(geo_dir) - if energies is not None and any(e is not None for e in energies): - optimized = True - min_e = extremum_list(energies, return_min=True) - conf_path = os.path.join(geo_dir, 'conformers_after_optimization.txt') - else: - optimized = False + if before_optimization: conf_path = os.path.join(geo_dir, 'conformers_before_optimization.txt') + else: + conf_path = os.path.join(geo_dir, 'conformers_after_optimization.txt') + min_e = extremum_list(energies, return_min=True) with open(conf_path, 'w') as f: content = '' - if optimized: + if before_optimization: + 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' for i, xyz in enumerate(xyzs): @@ -952,7 +952,7 @@ def save_conformers_file(project_directory: str, content += f'TS guess method: {ts_methods[i]}\n' if im_freqs is not None and im_freqs[i] is not None: content += f'Imaginary frequencies: {im_freqs[i]}\n' - if optimized: + if min_e is not None: if energies[i] == min_e: content += 'Relative Energy: 0 kJ/mol (lowest)' elif energies[i] is not None: From f751108207f05b45818866e0491dfbda824745aa Mon Sep 17 00:00:00 2001 From: Jintao Date: Sun, 26 May 2024 08:12:40 +0800 Subject: [PATCH 3/5] Define the attribute `conformer_energies` properly --- arc/scheduler.py | 1 + arc/species/species.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/arc/scheduler.py b/arc/scheduler.py index b6674db9e8..dbaef49b1c 100644 --- a/arc/scheduler.py +++ b/arc/scheduler.py @@ -1853,6 +1853,7 @@ def process_conformers(self, label): multiplicity=self.species_dict[label].multiplicity, charge=self.species_dict[label].charge, is_ts=False, + energies=self.species_dict[label].conformer_energies, before_optimization=True, ) # before optimization self.species_dict[label].conformers_before_opt = tuple(self.species_dict[label].conformers) diff --git a/arc/species/species.py b/arc/species/species.py index af81f40431..7c0df39d7c 100644 --- a/arc/species/species.py +++ b/arc/species/species.py @@ -1119,7 +1119,7 @@ def generate_conformers(self, ) if len(lowest_confs): self.conformers.extend([conf['xyz'] for conf in lowest_confs]) - self.conformer_energies.extend([None] * len(lowest_confs)) + self.conformer_energies.extend([conf['FF energy'] for conf in lowest_confs]) else: xyz = self.get_xyz(generate=False) if xyz is None or not xyz: From f9a2535b260128b387040c950aa6325f7e18fe61 Mon Sep 17 00:00:00 2001 From: Jintao Date: Sun, 26 May 2024 08:13:32 +0800 Subject: [PATCH 4/5] Modify the logic for conformer min_e --- arc/common.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/arc/common.py b/arc/common.py index 72aa79e16d..a99b9752f3 100644 --- a/arc/common.py +++ b/arc/common.py @@ -756,9 +756,7 @@ def extremum_list(lst: list, Returns: Optional[Union[int, None]] The entry with the minimal/maximal value. """ - if len(lst) == 0: - return None - elif all([entry is None for entry in lst]): + if lst is None or len(lst) == 0 or all([entry is None for entry in lst]): return None elif len(lst) == 1: return lst[0] From e9de000d510c9f9bd397067db20f82feeb01b122 Mon Sep 17 00:00:00 2001 From: Jintao Date: Sun, 26 May 2024 08:11:30 +0800 Subject: [PATCH 5/5] Modify the tests --- arc/plotter_test.py | 3 ++- arc/scheduler_test.py | 12 +++++++----- arc/species/species_test.py | 6 ++++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/arc/plotter_test.py b/arc/plotter_test.py index a81eca728a..f2dce747d1 100644 --- a/arc/plotter_test.py +++ b/arc/plotter_test.py @@ -92,7 +92,8 @@ def test_save_conformers_file(self): plotter.save_conformers_file(project_directory=project_directory, label=spc1.label, xyzs=spc1.conformers, level_of_theory='APFD/def2tzvp', multiplicity=spc1.multiplicity, charge=spc1.charge, is_ts=False, - energies=spc1.conformer_energies) + energies=spc1.conformer_energies, + before_optimization=True,) conf_file_path = os.path.join(project_directory, 'output', 'Species', label, 'geometry', 'conformers', 'conformers_before_optimization.txt') self.assertTrue(os.path.isfile(conf_file_path)) diff --git a/arc/scheduler_test.py b/arc/scheduler_test.py index be6e7a670a..7e08bba031 100644 --- a/arc/scheduler_test.py +++ b/arc/scheduler_test.py @@ -184,16 +184,18 @@ def test_conformers(self): xyzs=self.sched1.species_dict['C2H6'].conformers, level_of_theory=Level(method='CBS-QB3'), multiplicity=1, - charge=0) + charge=0, + before_optimization=True,) c2h6_conf_path = os.path.join(self.sched1.project_directory, 'output', 'Species', 'C2H6', 'geometry', 'conformers', 'conformers_before_optimization.txt') self.assertTrue(os.path.isfile(c2h6_conf_path)) with open(c2h6_conf_path, 'r') as f: lines = f.readlines() - self.assertEqual(lines[0], 'conformer 0:\n') - self.assertEqual(lines[1][0], 'C') - self.assertEqual(lines[9], '\n') - self.assertEqual(lines[10], 'SMILES: CC\n') + self.assertEqual(lines[0], 'Conformers for C2H6, computed using a force field:\n') + self.assertEqual(lines[2], 'conformer 0:\n') + self.assertEqual(lines[3][0], 'C') + self.assertEqual(lines[11], '\n') + self.assertEqual(lines[12], 'SMILES: CC\n') def test_check_negative_freq(self): """Test the check_negative_freq() method""" diff --git a/arc/species/species_test.py b/arc/species/species_test.py index 63a00e63bf..7a0b1a5658 100644 --- a/arc/species/species_test.py +++ b/arc/species/species_test.py @@ -1538,13 +1538,15 @@ def test_append_conformers(self): # test w/o energies save_conformers_file(project_directory=project_directory, label='vinoxy', xyzs=xyzs, - level_of_theory=Level(repr='level1'), multiplicity=2, charge=0) + level_of_theory=Level(repr='level1'), multiplicity=2, charge=0, + before_optimization=True,) self.assertTrue(os.path.isfile(os.path.join(project_directory, 'output', 'Species', 'vinoxy', 'geometry', 'conformers', 'conformers_before_optimization.txt'))) # test with energies save_conformers_file(project_directory=project_directory, label='vinoxy', xyzs=xyzs, - level_of_theory=Level(repr='level1'), multiplicity=2, charge=0, energies=energies) + level_of_theory=Level(repr='level1'), multiplicity=2, charge=0, + energies=energies, before_optimization=False,) self.assertTrue(os.path.isfile(os.path.join(project_directory, 'output', 'Species', 'vinoxy', 'geometry', 'conformers', 'conformers_after_optimization.txt')))