|
17 | 17 | from arc.level import Level |
18 | 18 | from arc.parser.parser import parse_normal_mode_displacement, parse_geometry |
19 | 19 | from arc.reaction import ARCReaction |
| 20 | +from arc.species.converter import xyz_from_data |
20 | 21 | from arc.species.species import ARCSpecies, TSGuess |
21 | 22 |
|
22 | 23 |
|
@@ -713,6 +714,126 @@ def test_check_imaginary_frequencies(self): |
713 | 714 | imaginary_freqs = [-500.80, -3.14] |
714 | 715 | self.assertTrue(ts.check_imaginary_frequencies(imaginary_freqs)) |
715 | 716 |
|
| 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 | + |
716 | 837 | @classmethod |
717 | 838 | def tearDownClass(cls): |
718 | 839 | """ |
|
0 commit comments