Skip to content

Commit 37d062e

Browse files
Merge pull request #278 from marvin-hansen/main
feat(deep_causality): Added Configurable Reasoning Modalities to Causal Collectins.
2 parents b485102 + 2ee0195 commit 37d062e

38 files changed

+1167
-542
lines changed

deep_causality/benches/benchmarks/bench_collection.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fn small_causality_collection_benchmark(criterion: &mut Criterion) {
2020

2121
criterion.bench_function("small_causality_collection_propagation", |bencher| {
2222
bencher.iter(|| {
23-
coll.evaluate_deterministic_propagation(&evidence, &AggregateLogic::All)
23+
coll.evaluate_deterministic(&evidence, &AggregateLogic::All)
2424
.unwrap()
2525
})
2626
});
@@ -32,7 +32,7 @@ fn medium_causality_collection_benchmark(criterion: &mut Criterion) {
3232

3333
criterion.bench_function("medium_causality_collection_propagation", |bencher| {
3434
bencher.iter(|| {
35-
coll.evaluate_deterministic_propagation(&evidence, &AggregateLogic::All)
35+
coll.evaluate_deterministic(&evidence, &AggregateLogic::All)
3636
.unwrap()
3737
})
3838
});
@@ -44,7 +44,7 @@ fn large_causality_collection_benchmark(criterion: &mut Criterion) {
4444

4545
criterion.bench_function("large_causality_collection_propagation", |bencher| {
4646
bencher.iter(|| {
47-
coll.evaluate_deterministic_propagation(&evidence, &AggregateLogic::All)
47+
coll.evaluate_deterministic(&evidence, &AggregateLogic::All)
4848
.unwrap()
4949
})
5050
});

deep_causality/src/traits/causable/causable_reasoning.rs

Lines changed: 51 additions & 129 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.
56-
fn evaluate_deterministic_propagation(
62+
/// Returns a `CausalityError` if any `Causable` item returns a non-deterministic effect.
63+
fn evaluate_deterministic(
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 broken. 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.
@@ -95,118 +84,51 @@ where
9584
///
9685
/// # Errors
9786
/// Returns a `CausalityError` if a `ContextualLink` is encountered.
98-
fn evaluate_probabilistic_propagation(
87+
fn evaluate_probabilistic(
9988
&self,
10089
effect: &PropagatingEffect,
101-
_logic: &AggregateLogic,
90+
logic: &AggregateLogic,
91+
threshold: NumericalValue,
10292
) -> Result<PropagatingEffect, CausalityError> {
103-
let mut cumulative_prob: NumericalValue = 1.0;
104-
105-
for cause in self.get_all_items() {
106-
let effect = cause.evaluate(effect)?;
107-
108-
match effect {
109-
PropagatingEffect::Probabilistic(p) => {
110-
cumulative_prob *= p;
111-
}
112-
PropagatingEffect::Deterministic(true) => {
113-
// This is equivalent to multiplying by 1.0, so we do nothing and continue.
114-
}
115-
PropagatingEffect::Deterministic(false) => {
116-
// If any link is deterministically false, the entire chain's probability is zero.
117-
return Ok(PropagatingEffect::Probabilistic(0.0));
118-
}
119-
PropagatingEffect::Halting => {
120-
// Halting always takes precedence and stops the chain.
121-
return Ok(PropagatingEffect::Halting);
122-
}
123-
PropagatingEffect::ContextualLink(_, _) => {
124-
// Contextual links are not meaningful in a probabilistic aggregation.
125-
return Err(CausalityError(
126-
"Encountered a ContextualLink in a probabilistic chain evaluation.".into(),
127-
));
128-
}
129-
_ => {
130-
// Other variants are not handled in this mode.
131-
return Err(CausalityError(format!(
132-
"evaluate_probabilistic_propagation encountered an unhandled effect: {effect:?}"
133-
)));
134-
}
135-
}
136-
}
137-
138-
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+
)
139100
}
140101

141102
/// Evaluates a linear chain of causes that may contain a mix of deterministic and
142-
/// probabilistic effects, aggregating them into a final effect.
103+
/// probabilistic effects, aggregating them into a final deterministic outcome.
104+
///
105+
/// This method converts all effects (`Deterministic`, `Probabilistic`, `Numerical`)
106+
/// into a numerical value (where true=1.0, false=0.0) and aggregates them by
107+
/// multiplication. The final cumulative probability is then compared against a
108+
/// threshold (0.5) to produce a final `Deterministic(true)` or `Deterministic(false)`.
109+
///
110+
/// This approach is robust, order-independent, and provides a consistent result.
143111
///
144112
/// # Arguments
145113
/// * `effect` - A single `PropagatingEffect` object that all causes will use.
146114
///
147115
/// # Errors
148-
/// Returns a `CausalityError` if a `ContextualLink` is encountered.
149-
fn evaluate_mixed_propagation(
116+
/// Returns a `CausalityError` if a `ContextualLink` is encountered, as it cannot be
117+
/// converted to a numerical probability.
118+
fn evaluate_mixed(
150119
&self,
151120
effect: &PropagatingEffect,
152-
_logic: &AggregateLogic,
121+
logic: &AggregateLogic,
122+
threshold: NumericalValue,
153123
) -> Result<PropagatingEffect, CausalityError> {
154-
// The chain starts as deterministically true. It can transition to probabilistic.
155-
let mut aggregated_effect = PropagatingEffect::Deterministic(true);
156-
157-
for cause in self.get_all_items() {
158-
let current_effect = cause.evaluate(effect)?;
159-
160-
// Update the aggregated effect based on the current effect.
161-
aggregated_effect = match (aggregated_effect, current_effect) {
162-
// Halting takes precedence over everything.
163-
(_, PropagatingEffect::Halting) => return Ok(PropagatingEffect::Halting),
164-
165-
// Deterministic false breaks the chain.
166-
(_, PropagatingEffect::Deterministic(false)) => {
167-
return Ok(PropagatingEffect::Deterministic(false));
168-
}
169-
170-
// ContextualLink is invalid in this context.
171-
(_, PropagatingEffect::ContextualLink(_, _)) => {
172-
return Err(CausalityError(
173-
"Encountered a ContextualLink in a mixed-chain evaluation.".into(),
174-
));
175-
}
176-
177-
// If the chain is deterministic and the new effect is true, it remains deterministic true.
178-
(
179-
PropagatingEffect::Deterministic(true),
180-
PropagatingEffect::Deterministic(true),
181-
) => PropagatingEffect::Deterministic(true),
182-
183-
// If the chain is deterministic and the new effect is probabilistic, the chain becomes probabilistic.
184-
(PropagatingEffect::Deterministic(true), PropagatingEffect::Probabilistic(p)) => {
185-
PropagatingEffect::Probabilistic(p)
186-
}
187-
188-
// If the chain is already probabilistic and the new effect is true, the probability is unchanged.
189-
(PropagatingEffect::Probabilistic(p), PropagatingEffect::Deterministic(true)) => {
190-
PropagatingEffect::Probabilistic(p)
191-
}
192-
193-
// If the chain is probabilistic and the new effect is also probabilistic, multiply them.
194-
(PropagatingEffect::Probabilistic(p1), PropagatingEffect::Probabilistic(p2)) => {
195-
PropagatingEffect::Probabilistic(p1 * p2)
196-
}
197-
198-
// Other combinations should not be possible due to the guards above.
199-
(agg, curr) => {
200-
return Err(CausalityError(format!(
201-
"Unhandled effect combination in mixed chain: Agg: {agg:?}, Curr: {curr:?}"
202-
)));
203-
}
204-
};
205-
}
206-
207-
Ok(aggregated_effect)
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+
)
208131
}
209-
210132
/// Generates an explanation by concatenating the `explain()` text of all causes.
211133
///
212134
/// Each explanation is formatted and separated by newlines.
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+
}

0 commit comments

Comments
 (0)