@@ -2888,12 +2888,12 @@ def check_freq_job(self,
28882888 f'Status is:\n { self .species_dict [label ].ts_checks } \n '
28892889 f'Searching for a better TS conformer...' )
28902890 # ── Graph: record NMD validation failure ──
2891- self .graph .add_decision_node (
2891+ nmd_fail_nid = self .graph .add_decision_node (
28922892 label = label ,
28932893 decision_kind = DecisionKind .ts_validation_nmd ,
28942894 outcome = 'Failed: normal mode displacement check' ,
28952895 )
2896- self .switch_ts (label )
2896+ self .switch_ts (label , triggered_by_nid = nmd_fail_nid )
28972897 switch_ts = True
28982898 if wrong_freq_message in self .output [label ]['warnings' ]:
28992899 self .output [label ]['warnings' ] = '' .join (self .output [label ]['warnings' ].split (wrong_freq_message ))
@@ -2961,14 +2961,14 @@ def check_negative_freq(self,
29612961 f'Status is:\n { self .species_dict [label ].ts_checks } \n '
29622962 f'Searching for a better TS conformer...' )
29632963 # ── Graph: record TS freq validation failure ──
2964- self .graph .add_decision_node (
2964+ freq_fail_nid = self .graph .add_decision_node (
29652965 label = label ,
29662966 decision_kind = DecisionKind .ts_validation_freq ,
29672967 criteria = {'neg_freqs' : [float (f ) for f in neg_freqs ],
29682968 'expected' : 1 },
29692969 outcome = f'Failed: { len (neg_freqs )} imaginary freqs, switching TS' ,
29702970 )
2971- self .switch_ts (label = label )
2971+ self .switch_ts (label = label , triggered_by_nid = freq_fail_nid )
29722972 return False
29732973 else :
29742974 logger .info (f'TS { label } has exactly one imaginary frequency: { neg_freqs [0 ]} ' )
@@ -3030,7 +3030,13 @@ def check_rxn_e0_by_spc(self, label: str):
30303030 if rxn .ts_species .ts_checks ['E0' ] is False :
30313031 logger .info (f'TS { rxn .ts_species .label } of reaction { rxn .label } did not pass the E0 check.\n '
30323032 f'Searching for a better TS conformer...\n ' )
3033- self .switch_ts (rxn .ts_label )
3033+ # ── Graph: record E0 validation failure ──
3034+ e0_fail_nid = self .graph .add_decision_node (
3035+ label = rxn .ts_label ,
3036+ decision_kind = DecisionKind .ts_validation_e0 ,
3037+ outcome = f'Failed: TS E0 not above both wells for { rxn .label } ' ,
3038+ )
3039+ self .switch_ts (rxn .ts_label , triggered_by_nid = e0_fail_nid )
30343040 if self .species_dict [rxn .ts_label ].ts_guesses_exhausted \
30353041 or self .species_dict [rxn .ts_label ].chosen_ts is None :
30363042 logger .warning (f'Could not find a valid TS conformer for { rxn .ts_label } '
@@ -3039,27 +3045,50 @@ def check_rxn_e0_by_spc(self, label: str):
30393045 # Restore E0 failure flag — switch_ts resets ts_checks via populate_ts_checks().
30403046 # check_all_done reads this to avoid overwriting convergence back to True.
30413047 self .species_dict [rxn .ts_label ].ts_checks ['E0' ] = False
3048+ elif rxn .ts_species .ts_checks ['E0' ] is True :
3049+ # ── Graph: record E0 validation pass ──
3050+ self .graph .add_decision_node (
3051+ label = rxn .ts_label ,
3052+ decision_kind = DecisionKind .ts_validation_e0 ,
3053+ outcome = f'Passed: TS E0 above both wells for { rxn .label } ' ,
3054+ )
3055+ # Also record e_elect check if it ran as a fallback.
3056+ if rxn .ts_species .ts_checks ['e_elect' ] is True :
3057+ self .graph .add_decision_node (
3058+ label = rxn .ts_label ,
3059+ decision_kind = DecisionKind .ts_validation_e_elect ,
3060+ outcome = f'Passed: TS e_elect above both wells for { rxn .label } ' ,
3061+ )
3062+ elif rxn .ts_species .ts_checks ['e_elect' ] is False :
3063+ self .graph .add_decision_node (
3064+ label = rxn .ts_label ,
3065+ decision_kind = DecisionKind .ts_validation_e_elect ,
3066+ outcome = f'Warning: TS e_elect not above both wells for { rxn .label } ' ,
3067+ )
30423068
3043- def switch_ts (self , label : str ):
3069+ def switch_ts (self , label : str , triggered_by_nid : Optional [ str ] = None ):
30443070 """
30453071 Try the next optimized TS guess in line if a previous TS guess was found to be wrong.
30463072
30473073 Args:
30483074 label (str): The TS species label.
3075+ triggered_by_nid (str, optional): Node ID of the validation decision that triggered this switch.
30493076 """
30503077 logger .info (f'Switching a TS guess for { label } ...' )
30513078 old_chosen = self .species_dict [label ].chosen_ts
30523079 self .determine_most_likely_ts_conformer (label = label ) # Look for a different TS guess.
30533080 new_chosen = self .species_dict [label ].chosen_ts
30543081 # ── Graph: record TS switch decision ──
3055- self .graph .add_decision_node (
3082+ switch_nid = self .graph .add_decision_node (
30563083 label = label ,
30573084 decision_kind = DecisionKind .ts_switch ,
30583085 criteria = {'old_chosen' : old_chosen , 'new_chosen' : new_chosen ,
30593086 'exhausted' : self .species_dict [label ].ts_guesses_exhausted },
30603087 outcome = f'Switched from TSG #{ old_chosen } to #{ new_chosen } '
30613088 if new_chosen is not None else 'All TS guesses exhausted' ,
30623089 )
3090+ if triggered_by_nid is not None :
3091+ self .graph .add_edge (triggered_by_nid , switch_nid , EdgeType .triggered_by )
30633092 self .delete_all_species_jobs (label = label ) # Delete other currently running jobs for this TS.
30643093 freq_path = os .path .join (self .project_directory , 'output' , 'rxns' , label , 'geometry' , 'freq.out' )
30653094 if os .path .isfile (freq_path ):
@@ -3239,17 +3268,25 @@ def check_irc_species(self, label: str):
32393268 if len (self .output [ts_label ]['paths' ]['irc' ]) == 2 :
32403269 irc_species_labels = self .species_dict [ts_label ].irc_label .split ()
32413270 if all (self .output [irc_label ]['paths' ]['geo' ] for irc_label in irc_species_labels ):
3271+ rxn = self .rxn_dict .get (self .species_dict [ts_label ].rxn_index , None )
32423272 check_irc_species_and_rxn (
32433273 xyz_1 = self .output [irc_species_labels [0 ]]['paths' ]['geo' ],
32443274 xyz_2 = self .output [irc_species_labels [1 ]]['paths' ]['geo' ],
3245- rxn = self . rxn_dict . get ( self . species_dict [ ts_label ]. rxn_index , None ) ,
3275+ rxn = rxn ,
32463276 )
3247- # ── Graph: record IRC validation decision ──
3277+ # ── Graph: record IRC validation decision with actual outcome ──
3278+ irc_result = rxn .ts_species .ts_checks ['IRC' ] if rxn is not None else None
3279+ if irc_result is True :
3280+ irc_outcome = 'Passed: IRC endpoints match expected R/P'
3281+ elif irc_result is False :
3282+ irc_outcome = 'Failed: IRC endpoints do not match expected R/P'
3283+ else :
3284+ irc_outcome = 'Inconclusive: could not determine IRC match'
32483285 self .graph .add_decision_node (
32493286 label = ts_label ,
32503287 decision_kind = DecisionKind .ts_validation_irc ,
3251- criteria = {'irc_species' : irc_species_labels },
3252- outcome = 'IRC validation completed' ,
3288+ criteria = {'irc_species' : irc_species_labels , 'result' : irc_result },
3289+ outcome = irc_outcome ,
32533290 )
32543291
32553292 def check_scan_job (self ,
@@ -3513,6 +3550,12 @@ def check_all_done(self, label: str):
35133550 all_converged = False
35143551 if label in self .output and all_converged :
35153552 self .output [label ]['convergence' ] = True
3553+ # ── Graph: record convergence gate ──
3554+ self .graph .add_decision_node (
3555+ label = label ,
3556+ decision_kind = DecisionKind .convergence_confirmed ,
3557+ outcome = f'All required calculations converged' ,
3558+ )
35163559 if self .species_dict [label ].is_ts :
35173560 self .species_dict [label ].make_ts_report ()
35183561 logger .info (self .species_dict [label ].ts_report + '\n ' )
0 commit comments