@@ -907,6 +907,104 @@ def test_switch_ts_cleanup(self, mock_run_opt):
907907 self .assertIsNone (sched .species_dict [ts_label ].ts_checks ['NMD' ])
908908 self .assertIsNone (sched .species_dict [ts_label ].ts_checks ['E0' ])
909909
910+ # Verify rotors convergence flag preserved as True (not blanket-reset to False).
911+ self .assertTrue (sched .output [ts_label ]['job_types' ]['rotors' ])
912+
913+ @patch ('arc.scheduler.Scheduler.run_opt_job' )
914+ def test_switch_ts_rotors_reset (self , mock_run_opt ):
915+ """Test that switch_ts resets rotors_dict when rotors are enabled, and preserves the None sentinel."""
916+ ts_xyz = str_to_xyz ("""N 0.91779059 0.51946178 0.00000000
917+ H 1.81402049 1.03819414 0.00000000
918+ H 0.00000000 0.00000000 0.00000000
919+ H 0.91779059 1.22790192 0.72426890""" )
920+
921+ ts_spc = ARCSpecies (label = 'TS_rot' , is_ts = True , xyz = ts_xyz , multiplicity = 1 , charge = 0 ,
922+ compute_thermo = False )
923+ ts_spc .ts_guesses = [
924+ TSGuess (index = 0 , method = 'heuristics' , success = True , energy = 100.0 , xyz = ts_xyz ,
925+ execution_time = '0:00:01' ),
926+ TSGuess (index = 1 , method = 'heuristics' , success = True , energy = 110.0 , xyz = ts_xyz ,
927+ execution_time = '0:00:01' ),
928+ ]
929+ ts_spc .ts_guesses [0 ].opt_xyz = ts_xyz
930+ ts_spc .ts_guesses [0 ].imaginary_freqs = [- 500.0 ]
931+ ts_spc .ts_guesses [1 ].opt_xyz = ts_xyz
932+ ts_spc .ts_guesses [1 ].imaginary_freqs = [- 400.0 ]
933+ ts_spc .chosen_ts = 0
934+ ts_spc .chosen_ts_list = [0 ]
935+ ts_spc .ts_guesses_exhausted = False
936+ # Simulate stale rotors from previous guess.
937+ ts_spc .rotors_dict = {0 : {'pivots' : [1 , 2 ], 'scan_path' : '' , 'success' : True }}
938+ ts_spc .number_of_rotors = 1
939+
940+ project_directory = os .path .join (ARC_PATH , 'Projects' ,
941+ 'arc_project_for_testing_delete_after_usage5' )
942+ self .addCleanup (shutil .rmtree , project_directory , ignore_errors = True )
943+ sched = Scheduler (project = 'test_switch_ts_rot' , ess_settings = self .ess_settings ,
944+ species_list = [ts_spc ],
945+ opt_level = Level (repr = default_levels_of_theory ['opt' ]),
946+ freq_level = Level (repr = default_levels_of_theory ['freq' ]),
947+ sp_level = Level (repr = default_levels_of_theory ['sp' ]),
948+ ts_guess_level = Level (repr = default_levels_of_theory ['ts_guesses' ]),
949+ project_directory = project_directory ,
950+ testing = True ,
951+ job_types = self .job_types2 , # rotors=True
952+ )
953+
954+ ts_label = 'TS_rot'
955+ sched .output [ts_label ]['job_types' ]['opt' ] = True
956+ sched .output [ts_label ]['job_types' ]['freq' ] = True
957+ sched .job_dict [ts_label ] = {'opt' : {}, 'freq' : {}, 'sp' : {}}
958+ sched .running_jobs [ts_label ] = []
959+
960+ sched .switch_ts (ts_label )
961+
962+ # rotors_dict should be reset so determine_rotors re-runs for the new geometry.
963+ self .assertEqual (sched .species_dict [ts_label ].rotors_dict , {})
964+ self .assertEqual (sched .species_dict [ts_label ].number_of_rotors , 0 )
965+
966+ # Now test that rotors_dict=None sentinel is preserved (species marked to skip rotors).
967+ ts_spc2 = ARCSpecies (label = 'TS_norot' , is_ts = True , xyz = ts_xyz , multiplicity = 1 , charge = 0 ,
968+ compute_thermo = False )
969+ ts_spc2 .ts_guesses = [
970+ TSGuess (index = 0 , method = 'heuristics' , success = True , energy = 100.0 , xyz = ts_xyz ,
971+ execution_time = '0:00:01' ),
972+ TSGuess (index = 1 , method = 'heuristics' , success = True , energy = 110.0 , xyz = ts_xyz ,
973+ execution_time = '0:00:01' ),
974+ ]
975+ ts_spc2 .ts_guesses [0 ].opt_xyz = ts_xyz
976+ ts_spc2 .ts_guesses [0 ].imaginary_freqs = [- 500.0 ]
977+ ts_spc2 .ts_guesses [1 ].opt_xyz = ts_xyz
978+ ts_spc2 .ts_guesses [1 ].imaginary_freqs = [- 400.0 ]
979+ ts_spc2 .chosen_ts = 0
980+ ts_spc2 .chosen_ts_list = [0 ]
981+ ts_spc2 .ts_guesses_exhausted = False
982+ ts_spc2 .rotors_dict = None # Sentinel: skip rotor scans.
983+
984+ project_directory2 = os .path .join (ARC_PATH , 'Projects' ,
985+ 'arc_project_for_testing_delete_after_usage6' )
986+ self .addCleanup (shutil .rmtree , project_directory2 , ignore_errors = True )
987+ sched2 = Scheduler (project = 'test_switch_ts_norot' , ess_settings = self .ess_settings ,
988+ species_list = [ts_spc2 ],
989+ opt_level = Level (repr = default_levels_of_theory ['opt' ]),
990+ freq_level = Level (repr = default_levels_of_theory ['freq' ]),
991+ sp_level = Level (repr = default_levels_of_theory ['sp' ]),
992+ ts_guess_level = Level (repr = default_levels_of_theory ['ts_guesses' ]),
993+ project_directory = project_directory2 ,
994+ testing = True ,
995+ job_types = self .job_types2 , # rotors=True
996+ )
997+
998+ ts_label2 = 'TS_norot'
999+ sched2 .output [ts_label2 ]['job_types' ]['opt' ] = True
1000+ sched2 .job_dict [ts_label2 ] = {'opt' : {}, 'freq' : {}, 'sp' : {}}
1001+ sched2 .running_jobs [ts_label2 ] = []
1002+
1003+ sched2 .switch_ts (ts_label2 )
1004+
1005+ # rotors_dict=None must be preserved — do not re-enable rotor scans.
1006+ self .assertIsNone (sched2 .species_dict [ts_label2 ].rotors_dict )
1007+
9101008 @classmethod
9111009 def tearDownClass (cls ):
9121010 """
0 commit comments