@@ -43,23 +43,25 @@ where
4343 /// Reasons over a subgraph by traversing all nodes reachable from a given start index.
4444 ///
4545 /// This method performs a Breadth-First Search (BFS) traversal of all descendants
46- /// of the `start_index`. It calls `evaluate` on each node and uses the resulting
47- /// `PropagatingEffect` to decide whether to continue the traversal down that path.
46+ /// of the `start_index`. The `PropagatingEffect` is passed sequentially:
47+ /// the output effect of a parent node becomes the input effect for its child node.
48+ /// The traversal continues as long as no `CausalityError` is returned.
4849 ///
4950 /// # Arguments
5051 ///
5152 /// * `start_index` - The index of the node to start the traversal from.
52- /// * `effect ` - The runtime effect to be passed to each node's evaluation function.
53+ /// * `initial_effect ` - The initial runtime effect to be passed to the starting node's evaluation function.
5354 ///
5455 /// # Returns
5556 ///
56- /// * `Ok(PropagatingEffect::Halting)` if any node in the traversal returns `Halting`.
57- /// * `Ok(PropagatingEffect::Deterministic(true))` if the traversal completes.
57+ /// * `Ok(PropagatingEffect)`: The final `PropagatingEffect` from the last successfully evaluated node
58+ /// in the main traversal path. `Deterministic(false)` now propagates and does not implicitly halt propagation.
59+ /// Only a `Causaloid` returning a `CausalityError` will abort the traversal.
5860 /// * `Err(CausalityError)` if the graph is not frozen, a node is missing, or an evaluation fails.
5961 fn evaluate_subgraph_from_cause (
6062 & self ,
6163 start_index : usize ,
62- effect : & PropagatingEffect ,
64+ initial_effect : & PropagatingEffect ,
6365 ) -> Result < PropagatingEffect , CausalityError > {
6466 if !self . is_frozen ( ) {
6567 return Err ( CausalityError (
@@ -73,68 +75,67 @@ where
7375 ) ) ) ;
7476 }
7577
76- let mut queue = VecDeque :: with_capacity ( self . number_nodes ( ) ) ;
78+ // Queue stores (node_index, incoming_effect_for_this_node)
79+ let mut queue = VecDeque :: < ( usize , PropagatingEffect ) > :: with_capacity ( self . number_nodes ( ) ) ;
7780 let mut visited = vec ! [ false ; self . number_nodes( ) ] ;
7881
79- queue. push_back ( start_index) ;
82+ // Initialize the queue with the starting node and the initial effect
83+ queue. push_back ( ( start_index, initial_effect. clone ( ) ) ) ;
8084 visited[ start_index] = true ;
8185
82- while let Some ( current_index) = queue. pop_front ( ) {
86+ // This will hold the effect of the last successfully processed node.
87+ // It's initialized with the initial_effect, in case the start_index node
88+ // itself prunes the path or is the only node.
89+ let mut last_propagated_effect = initial_effect. clone ( ) ;
90+
91+ while let Some ( ( current_index, incoming_effect) ) = queue. pop_front ( ) {
8392 let cause = self . get_causaloid ( current_index) . ok_or_else ( || {
8493 CausalityError ( format ! ( "Failed to get causaloid at index {current_index}" ) )
8594 } ) ?;
8695
87- // Evaluate the current cause using the new unified method.
88- // The same `effect` is passed to each node, and the node's CausalFn
89- // is responsible for extracting the data it needs from the effect map.
90- let effect = cause. evaluate ( effect) ?;
91-
92- match effect {
93- // If the cause is deterministically false, we stop traversing this path,
94- // but the overall subgraph reasoning does not fail or halt.
95- PropagatingEffect :: Deterministic ( false ) => continue ,
96-
97- // For any other propagating effect (true, probabilistic, etc.),
98- // we continue the traversal to its children.
99- _ => {
100- // The cause is valid, so add its children to the queue.
101- let children = self . get_graph ( ) . outbound_edges ( current_index) ?;
102- for child_index in children {
103- if !visited[ child_index] {
104- visited[ child_index] = true ;
105- queue. push_back ( child_index) ;
106- }
107- }
96+ // Evaluate the current cause using the incoming_effect.
97+ let result_effect = cause. evaluate ( & incoming_effect) ?;
98+
99+ // Update the last_propagated_effect with the result of the current node's evaluation.
100+ // This ensures the function returns the effect of the last node on the path.
101+ last_propagated_effect = result_effect. clone ( ) ;
102+
103+ // Only a CausalityError returned from cause.evaluate() will abort the traversal.
104+ let children = self . get_graph ( ) . outbound_edges ( current_index) ?;
105+ for child_index in children {
106+ if !visited[ child_index] {
107+ visited[ child_index] = true ;
108+ // Pass the result_effect of the current node to its children.
109+ queue. push_back ( ( child_index, result_effect. clone ( ) ) ) ;
108110 }
109111 }
110112 }
111113
112- // If the loop completes without halting, the reasoning is considered successful .
113- Ok ( PropagatingEffect :: Deterministic ( true ) )
114+ // If the loop completes, return the effect of the last node processed .
115+ Ok ( last_propagated_effect )
114116 }
115117
116118 /// Reasons over the shortest path between a start and stop cause.
117119 ///
118- /// It evaluates each node along the path. If any node fails evaluation or returns
119- /// a non-propagating effect, the reasoning stops.
120+ /// It evaluates each node sequentially along the path. The `PropagatingEffect` returned by
121+ /// one causaloid becomes the input for the next causaloid in the path. If any node
122+ /// fails evaluation or returns a non-propagating effect that prunes the path, the reasoning stops.
120123 ///
121124 /// # Arguments
122125 ///
123126 /// * `start_index` - The index of the start cause.
124127 /// * `stop_index` - The index of the stop cause.
125- /// * `effect ` - The runtime effect to be passed to each node's evaluation function.
128+ /// * `initial_effect ` - The runtime effect to be passed as input to the first node's evaluation function.
126129 ///
127130 /// # Returns
128131 ///
129- /// * `Ok(PropagatingEffect::Halting)` if any node on the path returns `Halting`.
130- /// * `Ok(PropagatingEffect::Deterministic(false))` if any node returns `Deterministic(false)`.
131- /// * `Ok(PropagatingEffect::Deterministic(true))` if all nodes on the path propagate successfully.
132+ /// * `Ok(PropagatingEffect)`: The final `PropagatingEffect` from the last evaluated node on the path.
132133 /// * `Err(CausalityError)` if the path cannot be found or an evaluation fails.
133134 fn evaluate_shortest_path_between_causes (
134135 & self ,
135136 start_index : usize ,
136137 stop_index : usize ,
137- effect : & PropagatingEffect ,
138+ initial_effect : & PropagatingEffect ,
138139 ) -> Result < PropagatingEffect , CausalityError > {
139140 if !self . is_frozen ( ) {
140141 return Err ( CausalityError (
@@ -147,21 +148,27 @@ where
147148 let cause = self . get_causaloid ( start_index) . ok_or_else ( || {
148149 CausalityError ( format ! ( "Failed to get causaloid at index {start_index}" ) )
149150 } ) ?;
150- return cause. evaluate ( effect ) ;
151+ return cause. evaluate ( initial_effect ) ;
151152 }
152153
153154 // get_shortest_path will handle checks for missing nodes.
154155 let path = self . get_shortest_path ( start_index, stop_index) ?;
155156
157+ let mut current_effect = initial_effect. clone ( ) ;
158+
156159 for index in path {
157160 let cause = self . get_causaloid ( index) . ok_or_else ( || {
158161 CausalityError ( format ! ( "Failed to get causaloid at index {index}" ) )
159162 } ) ?;
160163
161- let _ = cause. evaluate ( effect) ?;
164+ // Evaluate the current cause with the effect propagated from the previous node.
165+ // Then, overwrite the current_effect with the result of the evaluation, which then
166+ // serves as the input for the next node.
167+ // Only a CausalityError returned from cause.evaluate() will abort the traversal.
168+ current_effect = cause. evaluate ( & current_effect) ?;
162169 }
163170
164171 // If the loop completes, all nodes on the path were successfully evaluated.
165- Ok ( PropagatingEffect :: Deterministic ( true ) )
172+ Ok ( current_effect )
166173 }
167174}
0 commit comments