Skip to content

Commit c5dcea4

Browse files
committed
Tests: TS IRC check
1 parent 8ac5e82 commit c5dcea4

1 file changed

Lines changed: 121 additions & 0 deletions

File tree

arc/checks/ts_test.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from arc.level import Level
1818
from arc.parser.parser import parse_normal_mode_displacement, parse_geometry
1919
from arc.reaction import ARCReaction
20+
from arc.species.converter import xyz_from_data
2021
from arc.species.species import ARCSpecies, TSGuess
2122

2223

@@ -713,6 +714,126 @@ def test_check_imaginary_frequencies(self):
713714
imaginary_freqs = [-500.80, -3.14]
714715
self.assertTrue(ts.check_imaginary_frequencies(imaginary_freqs))
715716

717+
def test_perceive_irc_fragments_single_fragment(self):
718+
"""Test _perceive_irc_fragments for a single connected molecule (O=[C]COO)."""
719+
xyz_1 = parse_geometry(os.path.join(ARC_TESTING_PATH, 'irc', 'rxn_1_irc_1.out'))
720+
frags = ts._perceive_irc_fragments(xyz_1, charge=0)
721+
self.assertIsNotNone(frags)
722+
self.assertEqual(len(frags), 1)
723+
self.assertTrue(frags[0].is_isomorphic(ARCSpecies(label='R', smiles='O=[C]COO').mol))
724+
725+
def test_perceive_irc_fragments_two_fragments(self):
726+
"""Test _perceive_irc_fragments for well-separated water + methane."""
727+
coords = (
728+
(0.0000, 0.0000, 0.1173), # O
729+
(0.0000, 0.7572, -0.4692), # H
730+
(0.0000, -0.7572, -0.4692), # H
731+
(10.0000, 0.0000, 0.0000), # C
732+
(10.6276, 0.6276, 0.6276), # H
733+
(10.6276, -0.6276, -0.6276), # H
734+
(9.3724, 0.6276, -0.6276), # H
735+
(9.3724, -0.6276, 0.6276), # H
736+
)
737+
symbols = ('O', 'H', 'H', 'C', 'H', 'H', 'H', 'H')
738+
xyz = xyz_from_data(coords=coords, symbols=symbols)
739+
frags = ts._perceive_irc_fragments(xyz, charge=0)
740+
self.assertIsNotNone(frags)
741+
self.assertEqual(len(frags), 2)
742+
water_mol = ARCSpecies(label='water', smiles='O').mol
743+
methane_mol = ARCSpecies(label='methane', smiles='C').mol
744+
# Fragment order follows atom indices: water (atoms 0-2) then methane (atoms 3-7)
745+
self.assertTrue(frags[0].is_isomorphic(water_mol))
746+
self.assertTrue(frags[1].is_isomorphic(methane_mol))
747+
748+
def test_perceive_irc_fragments_charge_handling(self):
749+
"""Test that single-fragment systems use the given charge, multi-fragment use charge=0."""
750+
# Single fragment: use the IRC endpoint for O=[C]COO (charge=0, but tests the code path)
751+
xyz_1 = parse_geometry(os.path.join(ARC_TESTING_PATH, 'irc', 'rxn_1_irc_1.out'))
752+
frags = ts._perceive_irc_fragments(xyz_1, charge=0)
753+
self.assertIsNotNone(frags)
754+
self.assertEqual(len(frags), 1)
755+
756+
def test_match_fragments_to_species_single(self):
757+
"""Test _match_fragments_to_species with a single fragment."""
758+
r_spc = ARCSpecies(label='R', smiles='O=[C]COO')
759+
p_spc = ARCSpecies(label='P', smiles='O=CCO[O]')
760+
xyz_1 = parse_geometry(os.path.join(ARC_TESTING_PATH, 'irc', 'rxn_1_irc_1.out'))
761+
frags = ts._perceive_irc_fragments(xyz_1, charge=0)
762+
self.assertIsNotNone(frags)
763+
self.assertTrue(ts._match_fragments_to_species(frags, [r_spc.mol]))
764+
self.assertFalse(ts._match_fragments_to_species(frags, [p_spc.mol]))
765+
766+
def test_match_fragments_to_species_multi(self):
767+
"""Test _match_fragments_to_species with two well-separated fragments."""
768+
coords = (
769+
(0.0000, 0.0000, 0.1173),
770+
(0.0000, 0.7572, -0.4692),
771+
(0.0000, -0.7572, -0.4692),
772+
(10.0000, 0.0000, 0.0000),
773+
(10.6276, 0.6276, 0.6276),
774+
(10.6276, -0.6276, -0.6276),
775+
(9.3724, 0.6276, -0.6276),
776+
(9.3724, -0.6276, 0.6276),
777+
)
778+
symbols = ('O', 'H', 'H', 'C', 'H', 'H', 'H', 'H')
779+
xyz = xyz_from_data(coords=coords, symbols=symbols)
780+
frags = ts._perceive_irc_fragments(xyz, charge=0)
781+
self.assertIsNotNone(frags)
782+
783+
water_mol = ARCSpecies(label='water', smiles='O').mol
784+
methane_mol = ARCSpecies(label='methane', smiles='C').mol
785+
nh3_mol = ARCSpecies(label='NH3', smiles='N').mol
786+
787+
# Correct match (either order of expected species should work due to permutations)
788+
self.assertTrue(ts._match_fragments_to_species(frags, [water_mol, methane_mol]))
789+
self.assertTrue(ts._match_fragments_to_species(frags, [methane_mol, water_mol]))
790+
# Wrong species
791+
self.assertFalse(ts._match_fragments_to_species(frags, [water_mol, nh3_mol]))
792+
# Wrong count
793+
self.assertFalse(ts._match_fragments_to_species(frags, [water_mol]))
794+
self.assertFalse(ts._match_fragments_to_species(frags, [water_mol, methane_mol, nh3_mol]))
795+
796+
def test_match_fragments_to_species_empty(self):
797+
"""Test _match_fragments_to_species edge cases."""
798+
self.assertTrue(ts._match_fragments_to_species([], []))
799+
water_mol = ARCSpecies(label='water', smiles='O').mol
800+
self.assertFalse(ts._match_fragments_to_species([], [water_mol]))
801+
802+
def test_check_irc_isomorphism_path(self):
803+
"""Test that the full check_irc_species_and_rxn uses isomorphism when mol objects are available."""
804+
xyz_1 = parse_geometry(os.path.join(ARC_TESTING_PATH, 'irc', 'rxn_1_irc_1.out'))
805+
xyz_2 = parse_geometry(os.path.join(ARC_TESTING_PATH, 'irc', 'rxn_1_irc_2.out'))
806+
rxn = ARCReaction(r_species=[ARCSpecies(label='R', smiles='O=[C]COO', xyz=xyz_1)],
807+
p_species=[ARCSpecies(label='P', smiles='O=CCO[O]', xyz=xyz_2)])
808+
rxn.ts_species = ARCSpecies(label='TS', is_ts=True)
809+
# Both species have mol objects, so isomorphism path should be used
810+
self.assertIsNotNone(rxn.r_species[0].mol)
811+
self.assertIsNotNone(rxn.p_species[0].mol)
812+
ts.check_irc_species_and_rxn(xyz_1=xyz_1, xyz_2=xyz_2, rxn=rxn)
813+
self.assertTrue(rxn.ts_species.ts_checks['IRC'])
814+
815+
def test_check_irc_swapped_endpoints(self):
816+
"""Test that check_irc_species_and_rxn works when IRC endpoints are swapped."""
817+
xyz_1 = parse_geometry(os.path.join(ARC_TESTING_PATH, 'irc', 'rxn_1_irc_1.out'))
818+
xyz_2 = parse_geometry(os.path.join(ARC_TESTING_PATH, 'irc', 'rxn_1_irc_2.out'))
819+
rxn = ARCReaction(r_species=[ARCSpecies(label='R', smiles='O=[C]COO', xyz=xyz_1)],
820+
p_species=[ARCSpecies(label='P', smiles='O=CCO[O]', xyz=xyz_2)])
821+
rxn.ts_species = ARCSpecies(label='TS', is_ts=True)
822+
# Pass endpoints in reverse order (xyz_2 as first, xyz_1 as second)
823+
ts.check_irc_species_and_rxn(xyz_1=xyz_2, xyz_2=xyz_1, rxn=rxn)
824+
self.assertTrue(rxn.ts_species.ts_checks['IRC'])
825+
826+
def test_check_irc_wrong_species(self):
827+
"""Test that check_irc_species_and_rxn returns False for mismatched species."""
828+
xyz_1 = parse_geometry(os.path.join(ARC_TESTING_PATH, 'irc', 'rxn_1_irc_1.out'))
829+
xyz_2 = parse_geometry(os.path.join(ARC_TESTING_PATH, 'irc', 'rxn_1_irc_2.out'))
830+
# Use wrong product species
831+
rxn = ARCReaction(r_species=[ARCSpecies(label='R', smiles='O=[C]COO', xyz=xyz_1)],
832+
p_species=[ARCSpecies(label='P_wrong', smiles='CC', xyz=xyz_2)])
833+
rxn.ts_species = ARCSpecies(label='TS', is_ts=True)
834+
ts.check_irc_species_and_rxn(xyz_1=xyz_1, xyz_2=xyz_2, rxn=rxn)
835+
self.assertFalse(rxn.ts_species.ts_checks['IRC'])
836+
716837
@classmethod
717838
def tearDownClass(cls):
718839
"""

0 commit comments

Comments
 (0)