Skip to content

Commit 5fedbde

Browse files
committed
feat(deep_causality): Added Configurable Reasoning Modalities to Causal Collectins.
Signed-off-by: Marvin Hansen <[email protected]>
1 parent 030859d commit 5fedbde

File tree

10 files changed

+365
-107
lines changed

10 files changed

+365
-107
lines changed

deep_causality/src/traits/causable/causable_reasoning.rs

Lines changed: 38 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
* Copyright (c) "2025" . The DeepCausality Authors and Contributors. All Rights Reserved.
44
*/
55

6+
use crate::traits::causable::{
7+
causable_reasoning_deterministic, causable_reasoning_mixed, causable_reasoning_probabilistic,
8+
};
69
use crate::{
710
AggregateLogic, Causable, CausalityError, IdentificationValue, NumericalValue,
811
PropagatingEffect,
@@ -42,46 +45,32 @@ where
4245
// Default implementations for all other methods are provided below.
4346
//
4447

45-
/// Evaluates a linear chain of causes where each link is strictly expected to be deterministic.
48+
/// Evaluates a collection of `Causable` items, aggregating their deterministic
49+
/// boolean outcomes (`true`/`false`) based on a specified `AggregateLogic`.
4650
///
47-
/// The chain is considered active only if every single cause in the collection
48-
/// evaluates to `PropagatingEffect::Deterministic(true)`. If any cause evaluates
49-
/// to `Deterministic(false)`, the chain evaluation stops and returns that effect.
51+
/// This function requires that every item in the collection evaluates to a
52+
/// `PropagatingEffect::Deterministic` variant. If any item returns a different
53+
/// variant, the function will fail with a `CausalityError`, ensuring strict
54+
/// type checking at runtime.
5055
///
5156
/// # Arguments
52-
/// * `effect` - A single `PropagatingEffect` object (e.g., a Map or Graph) that all causes will use.
57+
/// * `effect` - A `PropagatingEffect` to be passed to each `Causable` item.
58+
/// * `logic` - The `AggregateLogic` (e.g., `All`, `Any`, `None`, `Some(k)`)
59+
/// that defines how the boolean results are combined into a final `Deterministic` outcome.
5360
///
5461
/// # Errors
55-
/// Returns a `CausalityError` if any cause in the chain produces a non-deterministic effect.
62+
/// Returns a `CausalityError` if any `Causable` item returns a non-deterministic effect.
5663
fn evaluate_deterministic_propagation(
5764
&self,
5865
effect: &PropagatingEffect,
59-
_logic: &AggregateLogic,
66+
logic: &AggregateLogic,
6067
) -> Result<PropagatingEffect, CausalityError> {
61-
for cause in self.get_all_items() {
62-
let effect = cause.evaluate(effect)?;
63-
64-
// This function enforces a strict deterministic contract.
65-
match effect {
66-
PropagatingEffect::Deterministic(true) => {
67-
// The link is active, continue to the next one.
68-
continue;
69-
}
70-
PropagatingEffect::Deterministic(false) => {
71-
// The chain is deterministically false b/c on causaloid evaluates to false. This is a valid final outcome.
72-
return Ok(PropagatingEffect::Deterministic(false));
73-
}
74-
_ => {
75-
// Any other effect type is a contract violation for this function.
76-
return Err(CausalityError(format!(
77-
"evaluate_deterministic_propagation encountered a non-deterministic effect: {effect:?}. Only Deterministic effects are allowed."
78-
)));
79-
}
80-
}
81-
}
82-
83-
// If the entire loop completes, all links were deterministically true.
84-
Ok(PropagatingEffect::Deterministic(true))
68+
// Delegate to private impl in causable_reasoning_deterministic
69+
causable_reasoning_deterministic::_evaluate_deterministic_logic(
70+
self.get_all_items(),
71+
effect,
72+
logic,
73+
)
8574
}
8675

8776
/// Evaluates a linear chain of causes where each link is expected to be probabilistic.
@@ -98,30 +87,16 @@ where
9887
fn evaluate_probabilistic_propagation(
9988
&self,
10089
effect: &PropagatingEffect,
101-
_logic: &AggregateLogic,
90+
logic: &AggregateLogic,
91+
threshold: NumericalValue,
10292
) -> Result<PropagatingEffect, CausalityError> {
103-
// 1.0 is the multiplicative identity for cumulative probability.
104-
let mut cumulative_prob: NumericalValue = 1.0;
105-
106-
for cause in self.get_all_items() {
107-
let effect = cause.evaluate(effect)?;
108-
109-
match effect {
110-
PropagatingEffect::Probabilistic(p) | PropagatingEffect::Numerical(p) => {
111-
cumulative_prob *= p;
112-
}
113-
114-
_ => {
115-
// Other variants are not handled in this mode.
116-
return Err(CausalityError(format!(
117-
"evaluate_probabilistic_propagation encountered a non-probabilistic effect: {effect:?}. Only probabilistic or numerical effects are allowed."
118-
)));
119-
}
120-
}
121-
}
122-
123-
// Convert final probability to a deterministic outcome based on a threshold.
124-
Ok(PropagatingEffect::Probabilistic(cumulative_prob))
93+
// Delegate to private impl in causable_reasoning_probabilistic
94+
causable_reasoning_probabilistic::_evaluate_probabilistic_logic(
95+
self.get_all_items(),
96+
effect,
97+
logic,
98+
threshold,
99+
)
125100
}
126101

127102
/// Evaluates a linear chain of causes that may contain a mix of deterministic and
@@ -143,38 +118,16 @@ where
143118
fn evaluate_mixed_propagation(
144119
&self,
145120
effect: &PropagatingEffect,
146-
_logic: &AggregateLogic,
121+
logic: &AggregateLogic,
122+
threshold: NumericalValue,
147123
) -> Result<PropagatingEffect, CausalityError> {
148-
// Start with 1.0, the multiplicative identity, to aggregate all effects numerically.
149-
let mut cumulative_prob: NumericalValue = 1.0;
150-
151-
for cause in self.get_all_items() {
152-
let current_effect = cause.evaluate(effect)?;
153-
154-
// Convert every effect to a numerical probability to ensure consistent, order-independent aggregation.
155-
let current_prob = match current_effect {
156-
PropagatingEffect::Deterministic(true) => 1.0,
157-
PropagatingEffect::Deterministic(false) => 0.0,
158-
PropagatingEffect::Probabilistic(p) => p,
159-
PropagatingEffect::Numerical(p) => p,
160-
// .
161-
_ => {
162-
// Other variants are not handled in this mode.
163-
return Err(CausalityError(format!(
164-
"evaluate_mixed_propagation encountered an unsupported effect: {effect:?}. Only probabilistic, deterministic, or numerical effects are allowed."
165-
)));
166-
}
167-
};
168-
169-
cumulative_prob *= current_prob;
170-
}
171-
172-
// Convert the final aggregated probability to a deterministic outcome based on a standard threshold.
173-
if cumulative_prob > 0.5 {
174-
Ok(PropagatingEffect::Deterministic(true))
175-
} else {
176-
Ok(PropagatingEffect::Deterministic(false))
177-
}
124+
// Delegate to private impl in causable_reasoning_mixed
125+
causable_reasoning_mixed::_evaluate_mixed_logic(
126+
self.get_all_items(),
127+
effect,
128+
logic,
129+
threshold,
130+
)
178131
}
179132
/// Generates an explanation by concatenating the `explain()` text of all causes.
180133
///
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* SPDX-License-Identifier: MIT
3+
* Copyright (c) "2025" . The DeepCausality Authors and Contributors. All Rights Reserved.
4+
*/
5+
6+
use crate::{AggregateLogic, Causable, CausalityError, PropagatingEffect};
7+
8+
/// Evaluates a collection of `Causable` items against a specific `AggregateLogic`.
9+
///
10+
/// This is a private helper function that encapsulates the core reasoning logic,
11+
/// allowing the public-facing trait method to remain a simple delegation.
12+
/// It is optimized to short-circuit for performance where possible.
13+
///
14+
/// # Arguments
15+
/// * `items` - A vector of references to `Causable` items.
16+
/// * `effect` - The `PropagatingEffect` to pass to each item's `evaluate` method.
17+
/// * `logic` - The aggregation logic to apply.
18+
///
19+
/// # Returns
20+
/// A `Result` containing the final `PropagatingEffect::Deterministic` outcome
21+
/// or a `CausalityError` if any item returns a non-deterministic effect.
22+
pub fn _evaluate_deterministic_logic<T: Causable>(
23+
items: Vec<&T>,
24+
effect: &PropagatingEffect,
25+
logic: &AggregateLogic,
26+
) -> Result<PropagatingEffect, CausalityError> {
27+
if items.is_empty() {
28+
return Err(CausalityError(
29+
"No Causaloids found to evaluate".to_string(),
30+
));
31+
}
32+
33+
let mut true_count = 0;
34+
35+
for cause in items {
36+
let evaluated_effect = cause.evaluate(effect)?;
37+
38+
let value = match evaluated_effect {
39+
PropagatingEffect::Deterministic(v) => v,
40+
_ => {
41+
// Strict contract: only deterministic effects are allowed.
42+
return Err(CausalityError(format!(
43+
"evaluate_deterministic_propagation encountered a non-deterministic effect: {evaluated_effect:?}. Only Deterministic effects are allowed."
44+
)));
45+
}
46+
};
47+
48+
match logic {
49+
AggregateLogic::All => {
50+
if !value {
51+
// Short-circuit on the first false.
52+
return Ok(PropagatingEffect::Deterministic(false));
53+
}
54+
}
55+
AggregateLogic::Any => {
56+
if value {
57+
// Short-circuit on the first true.
58+
return Ok(PropagatingEffect::Deterministic(true));
59+
}
60+
}
61+
AggregateLogic::None => {
62+
if value {
63+
// Short-circuit on the first true, as this violates the None condition.
64+
return Ok(PropagatingEffect::Deterministic(false));
65+
}
66+
}
67+
AggregateLogic::Some(k) => {
68+
if value {
69+
true_count += 1;
70+
if true_count >= *k {
71+
// Short-circuit as soon as the threshold is met.
72+
return Ok(PropagatingEffect::Deterministic(true));
73+
}
74+
}
75+
}
76+
}
77+
}
78+
79+
// If the loop completes, determine the final result for non-short-circuited paths.
80+
let final_result = match logic {
81+
// If we got here for All, it means no false values were found.
82+
AggregateLogic::All => true,
83+
// If we got here for Any, it means no true values were found.
84+
AggregateLogic::Any => false,
85+
// If we got here for None, it means no true values were found.
86+
AggregateLogic::None => true,
87+
// If we got here for Some(k), it means the threshold was never met.
88+
AggregateLogic::Some(k) => true_count >= *k, // This will be false.
89+
};
90+
91+
Ok(PropagatingEffect::Deterministic(final_result))
92+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* SPDX-License-Identifier: MIT
3+
* Copyright (c) "2025" . The DeepCausality Authors and Contributors. All Rights Reserved.
4+
*/
5+
6+
use crate::{AggregateLogic, Causable, CausalityError, NumericalValue, PropagatingEffect};
7+
8+
/// Evaluates a collection of `Causable` items that may contain a mix of deterministic and
9+
/// probabilistic effects, aggregating them into a final deterministic outcome.
10+
///
11+
/// This is a private helper function that encapsulates the core reasoning logic,
12+
/// allowing the public-facing trait method to remain a simple delegation.
13+
/// It is optimized to short-circuit for performance where possible.
14+
///
15+
/// # Arguments
16+
/// * `items` - A vector of references to `Causable` items.
17+
/// * `effect` - The `PropagatingEffect` to pass to each item's `evaluate` method.
18+
/// * `logic` - The aggregation logic to apply.
19+
/// * `threshold` - The numerical threshold used to convert probabilistic effects to boolean.
20+
///
21+
/// # Returns
22+
/// A `Result` containing the final `PropagatingEffect` outcome.
23+
/// For `AggregateLogic::All`, it returns `PropagatingEffect::Deterministic` after applying the threshold.
24+
/// For `Any`, `None`, and `Some(k)`, it returns `PropagatingEffect::Deterministic`.
25+
/// Returns a `CausalityError` if any item returns an unsupported effect type.
26+
pub fn _evaluate_mixed_logic<T: Causable>(
27+
items: Vec<&T>,
28+
effect: &PropagatingEffect,
29+
logic: &AggregateLogic,
30+
threshold: NumericalValue,
31+
) -> Result<PropagatingEffect, CausalityError> {
32+
if items.is_empty() {
33+
return Err(CausalityError(
34+
"No Causaloids found to evaluate".to_string(),
35+
));
36+
}
37+
38+
match logic {
39+
AggregateLogic::All => {
40+
let mut cumulative_prob: NumericalValue = 1.0;
41+
for cause in items {
42+
let current_effect = cause.evaluate(effect)?;
43+
let current_prob = match current_effect {
44+
PropagatingEffect::Deterministic(true) => 1.0,
45+
PropagatingEffect::Deterministic(false) => 0.0,
46+
PropagatingEffect::Probabilistic(p) => p,
47+
PropagatingEffect::Numerical(p) => p,
48+
_ => {
49+
return Err(CausalityError(format!(
50+
"evaluate_mixed_propagation encountered an unsupported effect: {current_effect:?}. Only probabilistic, deterministic, or numerical effects are allowed."
51+
)));
52+
}
53+
};
54+
cumulative_prob *= current_prob;
55+
}
56+
Ok(PropagatingEffect::Deterministic(
57+
cumulative_prob > threshold,
58+
))
59+
}
60+
_ => {
61+
let mut true_count = 0;
62+
for cause in items {
63+
let evaluated_effect = cause.evaluate(effect)?;
64+
let value = match evaluated_effect {
65+
PropagatingEffect::Deterministic(b) => b,
66+
PropagatingEffect::Probabilistic(p) | PropagatingEffect::Numerical(p) => {
67+
p > threshold
68+
}
69+
_ => {
70+
return Err(CausalityError(format!(
71+
"evaluate_mixed_propagation encountered an unsupported effect: {evaluated_effect:?}. Only probabilistic, numerical, or deterministic effects are allowed."
72+
)));
73+
}
74+
};
75+
76+
match logic {
77+
AggregateLogic::Any => {
78+
if value {
79+
return Ok(PropagatingEffect::Deterministic(true));
80+
}
81+
}
82+
AggregateLogic::None => {
83+
if value {
84+
return Ok(PropagatingEffect::Deterministic(false));
85+
}
86+
}
87+
AggregateLogic::Some(k) => {
88+
if value {
89+
true_count += 1;
90+
if true_count >= *k {
91+
return Ok(PropagatingEffect::Deterministic(true));
92+
}
93+
}
94+
}
95+
_ => unreachable!(), // All is handled above.
96+
}
97+
}
98+
99+
let final_result = match logic {
100+
AggregateLogic::Any => false,
101+
AggregateLogic::None => true,
102+
AggregateLogic::Some(k) => true_count >= *k,
103+
_ => unreachable!(), // All is handled above.
104+
};
105+
Ok(PropagatingEffect::Deterministic(final_result))
106+
}
107+
}
108+
}

0 commit comments

Comments
 (0)