|
36 | 36 | from arc.job.ssh import SSHClient |
37 | 37 | from arc.output import write_output_yml |
38 | 38 | from arc.processor import process_arc_project, resolve_neb_level |
| 39 | +from arc.provenance import DecisionKind, EdgeType |
39 | 40 | from arc.reaction import ARCReaction |
40 | 41 | from arc.scheduler import Scheduler |
41 | 42 | from arc.species.converter import str_to_xyz |
@@ -671,6 +672,70 @@ def execute(self) -> dict: |
671 | 672 | log_footer(execution_time=self.execution_time) |
672 | 673 | return status_dict |
673 | 674 |
|
| 675 | + def _add_arkane_provenance_nodes(self): |
| 676 | + """Add Arkane computation and result nodes to the provenance graph. |
| 677 | +
|
| 678 | + For each converged species with thermo results, creates: |
| 679 | + convergence_confirmed → calc(statmech_thermo) → data(thermo) |
| 680 | +
|
| 681 | + For each converged reaction with kinetics results, creates: |
| 682 | + convergence_confirmed → calc(statmech_kinetics) → data(kinetics) |
| 683 | + """ |
| 684 | + graph = self.scheduler.graph |
| 685 | + for spc in self.scheduler.species_dict.values(): |
| 686 | + if spc.is_ts or getattr(spc.thermo, 'H298', None) is None: |
| 687 | + continue |
| 688 | + spc_nid = graph.find_species_node(spc.label) |
| 689 | + if spc_nid is None: |
| 690 | + continue |
| 691 | + # Insert a CalculationNode for the Arkane thermo computation. |
| 692 | + calc_nid = graph.add_calculation_node( |
| 693 | + label=spc.label, |
| 694 | + job_name='arkane_thermo', |
| 695 | + job_type='statmech_thermo', |
| 696 | + job_adapter=self.thermo_adapter, |
| 697 | + status='done', |
| 698 | + ) |
| 699 | + graph.add_edge(spc_nid, calc_nid, EdgeType.belongs_to) |
| 700 | + # Link from convergence gate if it exists. |
| 701 | + conv_nodes = graph.query(decision_kind=DecisionKind.convergence_confirmed, label=spc.label) |
| 702 | + for conv_node in conv_nodes: |
| 703 | + graph.add_edge(conv_node.node_id, calc_nid, EdgeType.triggered_by) |
| 704 | + thermo_nid = graph.add_data_node( |
| 705 | + label=spc.label, |
| 706 | + data_kind='thermo', |
| 707 | + value=f'H298={spc.thermo.H298:.1f} kJ/mol, S298={spc.thermo.S298:.1f} J/mol/K', |
| 708 | + ) |
| 709 | + graph.add_edge(calc_nid, thermo_nid, EdgeType.output_of) |
| 710 | + for rxn in self.scheduler.rxn_list: |
| 711 | + if rxn.kinetics is None: |
| 712 | + continue |
| 713 | + ts_nid = graph.find_species_node(rxn.ts_label) |
| 714 | + if ts_nid is None: |
| 715 | + continue |
| 716 | + # Insert a CalculationNode for the Arkane kinetics computation. |
| 717 | + calc_nid = graph.add_calculation_node( |
| 718 | + label=rxn.ts_label, |
| 719 | + job_name='arkane_kinetics', |
| 720 | + job_type='statmech_kinetics', |
| 721 | + job_adapter=self.kinetics_adapter, |
| 722 | + status='done', |
| 723 | + ) |
| 724 | + graph.add_edge(ts_nid, calc_nid, EdgeType.belongs_to) |
| 725 | + # Link from TS convergence gate if it exists. |
| 726 | + conv_nodes = graph.query(decision_kind=DecisionKind.convergence_confirmed, label=rxn.ts_label) |
| 727 | + for conv_node in conv_nodes: |
| 728 | + graph.add_edge(conv_node.node_id, calc_nid, EdgeType.triggered_by) |
| 729 | + ea = rxn.kinetics.get('Ea') |
| 730 | + ea_str = f', Ea={ea[0]:.1f} {ea[1]}' if ea else '' |
| 731 | + kinetics_nid = graph.add_data_node( |
| 732 | + label=rxn.ts_label, |
| 733 | + data_kind='kinetics', |
| 734 | + value=f'{rxn.label}{ea_str}', |
| 735 | + ) |
| 736 | + graph.add_edge(calc_nid, kinetics_nid, EdgeType.output_of) |
| 737 | + graph.save(self.scheduler.graph_path) |
| 738 | + |
674 | 739 | def save_project_info_file(self): |
675 | 740 | """ |
676 | 741 | Save a project info file. |
|
0 commit comments