Skip to content

feat(deep_causality): Added Programmatic Verification of Model Assumptions #277

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 31, 2025

Conversation

marvin-hansen
Copy link
Member

@marvin-hansen marvin-hansen commented Jul 30, 2025

User description

This PR introduces a formal mechanism to programmatically
verify the assumptions of a Model in deep_causality.

Describe your changes

Key Changes:

  • New AssumptionError Enum: A dedicated error enum
    AssumptionError is introduced to handle failures related to
    assumption verification.
  • Fallible EvalFn Signature: The EvalFn type alias is
    updated to return a Result<bool, AssumptionError>, allowing
    assumption functions to fail gracefully.
  • Transferable Trait: A new Transferable trait is
    defined and implemented for the Model struct, providing a
    standardized verify_assumptions method.
  • Comprehensive Unit Tests: New unit tests are added for
    AssumptionError and Model::verify_assumptions to cover all
    success and error cases.

Issue ticket number and link

Closes #275

Code checklist before requesting a review

  • I have signed the DCO?
  • All tests are passing when running make test?
  • No errors or security vulnerabilities are reported by make check?

For details on make, please see BUILD.md

Note: The CI runs all of the above and fixing things before they hit CI speeds
up the review and merge process. Thank you.


PR Type

Enhancement


Description

  • Enhanced causable reasoning with configurable aggregate logic

  • Added programmatic model assumption verification system

  • Introduced RelayTo effect for causaloid dispatching

  • Updated assumption functions to return Result types


Diagram Walkthrough

flowchart LR
  A["CausableReasoning Trait"] --> B["AggregateLogic Enum"]
  A --> C["RelayTo Effect Support"]
  D["Model Struct"] --> E["Transferable Trait"]
  E --> F["verify_assumptions Method"]
  G["Assumption Functions"] --> H["Result<bool, AssumptionError>"]
  I["PropagatingEffect"] --> J["AssumptionError Handling"]
Loading

File Walkthrough

Relevant files
Enhancement
10 files
causable_reasoning.rs
Enhanced causable reasoning with aggregate logic                 
+462/-82
aggregate_logic.rs
Added AggregateLogic enum for configurable reasoning         
+25/-0   
mod.rs
Added Transferable trait for model verification                   
+22/-0   
transferable.rs
Implemented Transferable trait for Model                                 
+44/-0   
assumption_error.rs
Added AssumptionError enum for assumption failures             
+38/-0   
mod.rs
Updated assumable traits with Result types                             
+25/-7   
assumable.rs
Updated assumption verification to return Result                 
+9/-4     
alias_function.rs
Updated EvalFn to return Result type                                         
+2/-2     
mod.rs
Added get_item_by_id method implementations                           
+19/-26 
collections.rs
Added macros for item lookup functionality                             
+38/-0   
Tests
9 files
causable_vec_tests.rs
Updated tests with AggregateLogic parameter                           
+28/-21 
assumable_btree_map_tests.rs
Updated assumable tests with PropagatingEffect                     
+48/-24 
assumable_vec_deque_tests.rs
Updated assumable tests with PropagatingEffect                     
+49/-24 
assumable_hash_map_tests.rs
Updated assumable tests with PropagatingEffect                     
+48/-24 
assumable_arr_tests.rs
Updated assumable tests with PropagatingEffect                     
+48/-24 
assumable_vec_tests.rs
Updated assumable tests with PropagatingEffect                     
+48/-24 
model_tests.rs
Added model assumption verification tests                               
+62/-23 
assumption_tests.rs
Updated assumption tests with Result handling                       
+34/-21 
assumption_error_tests.rs
Added tests for AssumptionError enum                                         
+27/-0   
Bug fix
1 files
mod.rs
Fixed type references in PropagatingEffect                             
+13/-13 
Additional files
15 files
bench_collection.rs +12/-3   
mod.rs +3/-1     
lib.rs +3/-0     
mod.rs +1/-0     
debug.rs +1/-1     
mod.rs +1/-0     
mod.rs +1/-0     
test_utils.rs +2/-2     
mod.rs +2/-0     
causable_arr_tests.rs +11/-7   
causable_btree_map_tests.rs +10/-6   
causable_map_tests.rs +13/-8   
causable_vec_deque_tests.rs +10/-6   
mod.rs +2/-2     
lib.rs +15/-0   

@marvin-hansen marvin-hansen self-assigned this Jul 30, 2025
@qodo-merge-pro qodo-merge-pro bot changed the title feat(deep_causality): Added Programmatic Verification of Model Assumptions feat(deep_causality): Added Programmatic Verification of Model Assumptions Jul 30, 2025
Copy link

codecov bot commented Jul 30, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.67%. Comparing base (a569017) to head (a8fd18c).
⚠️ Report is 12 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #277      +/-   ##
==========================================
+ Coverage   95.56%   95.67%   +0.11%     
==========================================
  Files         231      234       +3     
  Lines        6133     6225      +92     
==========================================
+ Hits         5861     5956      +95     
+ Misses        272      269       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis ✅

275 - PR Code Verified

Compliant requirements:

• AssumptionError enum created with specified variants
• EvalFn signature updated to return Result<bool, AssumptionError>
• Assumption struct updated for fallible EvalFn
• Transferable trait created with verify_assumptions method
• Model struct implements Transferable trait
• Project compiles successfully
• Unit tests added for assumption verification

Requires further human verification:

• Verify that all edge cases in verify_assumptions are properly handled during runtime testing
• Confirm that error propagation works correctly in complex model scenarios

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Complex Logic

The evaluate_deterministic_propagation, evaluate_probabilistic_propagation, and evaluate_mixed_propagation methods have become significantly more complex with the addition of AggregateLogic handling and RelayTo effect resolution. The nested match statements and logic branches should be carefully reviewed for correctness and potential edge cases.

fn evaluate_deterministic_propagation(
    &self,
    effect: &PropagatingEffect,
    logic: &AggregateLogic,
) -> Result<PropagatingEffect, CausalityError> {
    let mut resolved_effects = Vec::new();

    for cause in self.get_all_items() {
        let current_effect = cause.evaluate(effect)?;

        let resolved_effect = match current_effect {
            PropagatingEffect::RelayTo(target_id, inner_effect) => {
                let target_causaloid = self
                    .get_item_by_id(target_id as IdentificationValue)
                    .ok_or_else(|| {
                        CausalityError(format!(
                            "Relay target causaloid with ID {target_id} not found."
                        ))
                    })?;
                target_causaloid.evaluate(&inner_effect)?
            }
            _ => current_effect,
        };
        resolved_effects.push(resolved_effect);
    }

    match logic {
        AggregateLogic::All => {
            let mut last_true_effect = PropagatingEffect::Deterministic(true);
            for res_effect in resolved_effects {
                match res_effect {
                    PropagatingEffect::Deterministic(true) => {
                        last_true_effect = res_effect;
                    }
                    PropagatingEffect::Deterministic(false) => {
                        return Ok(PropagatingEffect::Deterministic(false));
                    }
                    PropagatingEffect::Halting => {
                        return Ok(PropagatingEffect::Halting);
                    }
                    _ => {
                        return Err(CausalityError(format!(
                            "evaluate_deterministic_propagation (All) encountered a non-deterministic effect: {res_effect:?}. Only Deterministic effects are allowed."
                        )));
                    }
                }
            }
            Ok(last_true_effect)
        }
        AggregateLogic::Any => {
            let mut has_true = false;
            let mut last_effect = PropagatingEffect::Halting; // Default to Halting if no true or false found
            for res_effect in resolved_effects {
                match res_effect {
                    PropagatingEffect::Deterministic(true) => {
                        has_true = true;
                        last_effect = res_effect;
                    }
                    PropagatingEffect::Deterministic(false) => {
                        last_effect = res_effect;
                    }
                    PropagatingEffect::Halting => {
                        return Ok(PropagatingEffect::Halting);
                    }
                    _ => {
                        return Err(CausalityError(format!(
                            "evaluate_deterministic_propagation (Any) encountered a non-deterministic effect: {res_effect:?}. Only Deterministic effects are allowed."
                        )));
                    }
                }
            }
            if has_true {
                Ok(last_effect)
            } else {
                Ok(PropagatingEffect::Deterministic(false))
            }
        }
        AggregateLogic::None => {
            let mut all_false = true;
            for res_effect in resolved_effects {
                match res_effect {
                    PropagatingEffect::Deterministic(true) => {
                        all_false = false;
                        break;
                    }
                    PropagatingEffect::Deterministic(false) => continue,
                    PropagatingEffect::None => {
                        return Ok(PropagatingEffect::None);
                    }
                    PropagatingEffect::Halting => {
                        return Ok(PropagatingEffect::Halting);
                    }
                    _ => {
                        return Err(CausalityError(format!(
                            "evaluate_deterministic_propagation (None) encountered a non-deterministic effect: {res_effect:?}. Only Deterministic effects are allowed."
                        )));
                    }
                }
            }
            if all_false {
                Ok(PropagatingEffect::Deterministic(true))
            } else {
                Ok(PropagatingEffect::Deterministic(false))
            }
        }
        AggregateLogic::Some(k) => {
            let mut true_count = 0;
            for res_effect in resolved_effects {
                match res_effect {
                    PropagatingEffect::Deterministic(true) => {
                        true_count += 1;
                        continue;
                    }
                    PropagatingEffect::Deterministic(false) => {}
                    PropagatingEffect::Halting => {
                        return Ok(PropagatingEffect::Halting);
                    }
                    _ => {
                        return Err(CausalityError(format!(
                            "evaluate_deterministic_propagation (Some) encountered a non-deterministic effect: {res_effect:?}. Only Deterministic effects are allowed."
                        )));
                    }
                }
            }
            if true_count >= *k {
                Ok(PropagatingEffect::Deterministic(true))
            } else {
                Ok(PropagatingEffect::Deterministic(false))
            }
        }
    }
}

/// Evaluates a linear chain of causes where each link is expected to be probabilistic.
///
/// This method aggregates the effects by multiplying their probabilities, assuming
/// independence. It handles deterministic effects by treating `true` as a probability
/// of 1.0 and `false` as 0.0.
///
/// # Arguments
/// * `effect` - A single `PropagatingEffect` object that all causes will use.
///
/// # Errors
/// Returns a `CausalityError` if a `ContextualLink` is encountered.
fn evaluate_probabilistic_propagation(
    &self,
    effect: &PropagatingEffect,
    logic: &AggregateLogic,
) -> Result<PropagatingEffect, CausalityError> {
    let mut resolved_effects = Vec::new();

    for cause in self.get_all_items() {
        let current_effect = cause.evaluate(effect)?;

        let resolved_effect = match current_effect {
            PropagatingEffect::RelayTo(target_id, inner_effect) => {
                let target_causaloid = self
                    .get_item_by_id(target_id as IdentificationValue)
                    .ok_or_else(|| {
                        CausalityError(format!(
                            "Relay target causaloid with ID {target_id} not found."
                        ))
                    })?;
                target_causaloid.evaluate(&inner_effect)?
            }
            _ => current_effect,
        };
        resolved_effects.push(resolved_effect);
    }

    match logic {
        AggregateLogic::All => {
            let mut cumulative_prob: NumericalValue = 1.0;
            for res_effect in resolved_effects {
                match res_effect {
                    PropagatingEffect::None => {
                        return Ok(PropagatingEffect::None);
                    }
                    PropagatingEffect::Halting => {
                        return Ok(PropagatingEffect::Halting);
                    }

                    PropagatingEffect::Probabilistic(p) => {
                        cumulative_prob *= p;
                        if cumulative_prob == 0.0 {
                            return Ok(PropagatingEffect::Probabilistic(0.0));
                        }
                    }
                    PropagatingEffect::Deterministic(true) => {
                        // Equivalent to multiplying by 1.0
                    }
                    PropagatingEffect::Deterministic(false) => {
                        return Ok(PropagatingEffect::Probabilistic(0.0));
                    }

                    _ => {
                        return Err(CausalityError(format!(
                            "evaluate_probabilistic_propagation (All) encountered an unhandled effect: {res_effect:?}"
                        )));
                    }
                }
            }
            Ok(PropagatingEffect::Probabilistic(cumulative_prob))
        }
        AggregateLogic::Any => {
            for res_effect in resolved_effects {
                match res_effect {
                    PropagatingEffect::Probabilistic(p) => {
                        if p > 0.0 {
                            return Ok(PropagatingEffect::Probabilistic(p));
                        }
                    }
                    PropagatingEffect::Deterministic(true) => {
                        return Ok(PropagatingEffect::Deterministic(true));
                    }
                    PropagatingEffect::Deterministic(false) => {}
                    PropagatingEffect::Halting => {
                        return Ok(PropagatingEffect::Halting);
                    }
                    _ => {
                        return Err(CausalityError(format!(
                            "evaluate_probabilistic_propagation (Any) encountered an unhandled effect: {res_effect:?}"
                        )));
                    }
                }
            }
            Ok(PropagatingEffect::Probabilistic(0.0))
        }
        AggregateLogic::None => {
            for res_effect in resolved_effects {
                match res_effect {
                    PropagatingEffect::Probabilistic(p) => {
                        if p > 0.0 {
                            return Ok(PropagatingEffect::Probabilistic(0.0));
                        }
                    }
                    PropagatingEffect::Deterministic(true) => {
                        return Ok(PropagatingEffect::Probabilistic(0.0));
                    }
                    PropagatingEffect::Deterministic(false) => {}
                    PropagatingEffect::Halting => {
                        return Ok(PropagatingEffect::Halting);
                    }
                    _ => {
                        return Err(CausalityError(format!(
                            "evaluate_probabilistic_propagation (None) encountered an unhandled effect: {res_effect:?}"
                        )));
                    }
                }
            }
            Ok(PropagatingEffect::Probabilistic(1.0))
        }
        AggregateLogic::Some(k) => {
            let mut success_count = 0;
            let mut last_successful_effect = PropagatingEffect::Probabilistic(0.0);
            for res_effect in resolved_effects {
                match res_effect {
                    PropagatingEffect::Probabilistic(p) => {
                        if p > 0.0 {
                            success_count += 1;
                            last_successful_effect = res_effect;
                        }
                    }
                    PropagatingEffect::Deterministic(true) => {
                        success_count += 1;
                        last_successful_effect = res_effect;
                    }
                    PropagatingEffect::Deterministic(false) => {}
                    PropagatingEffect::Halting => {
                        return Ok(PropagatingEffect::Halting);
                    }
                    _ => {
                        return Err(CausalityError(format!(
                            "evaluate_probabilistic_propagation (Some) encountered an unhandled effect: {res_effect:?}"
                        )));
                    }
                }
            }
            if success_count >= *k {
                Ok(last_successful_effect)
            } else {
                Ok(PropagatingEffect::Probabilistic(0.0))
            }
        }
    }
}

/// Evaluates a linear chain of causes that may contain a mix of deterministic and
/// probabilistic effects, aggregating them into a final effect.
///
/// # Arguments
/// * `effect` - A single `PropagatingEffect` object that all causes will use.
///
/// # Errors
/// Returns a `CausalityError` if a `ContextualLink` is encountered.
fn evaluate_mixed_propagation(
    &self,
    effect: &PropagatingEffect,
    logic: &AggregateLogic,
) -> Result<PropagatingEffect, CausalityError> {
    let mut resolved_effects = Vec::new();

    for cause in self.get_all_items() {
        let current_effect = cause.evaluate(effect)?;

        let resolved_effect = match current_effect {
            PropagatingEffect::RelayTo(target_id, inner_effect) => {
                let target_causaloid = self
                    .get_item_by_id(target_id as IdentificationValue)
                    .ok_or_else(|| {
                        CausalityError(format!(
                            "Relay target causaloid with ID {target_id} not found."
                        ))
                    })?;
                target_causaloid.evaluate(&inner_effect)?
            }
            _ => current_effect,
        };
        resolved_effects.push(resolved_effect);
    }

    match logic {
        AggregateLogic::All => {
            let mut all_true = true;
            let mut last_successful_effect = PropagatingEffect::Deterministic(true);
            for res_effect in resolved_effects {
                match res_effect {
                    PropagatingEffect::Deterministic(true) => {
                        last_successful_effect = res_effect;
                    }
                    PropagatingEffect::Probabilistic(p) => {
                        if p > 0.0 {
                            last_successful_effect = res_effect;
                        } else {
                            all_true = false;
                            break;
                        }
                    }
                    PropagatingEffect::Deterministic(false) => {
                        all_true = false;
                        break;
                    }
                    PropagatingEffect::Halting => {
                        return Ok(PropagatingEffect::Halting);
                    }
                    PropagatingEffect::ContextualLink(_, _) => {
                        return Err(CausalityError(
                            "evaluate_mixed_propagation (All) encountered a ContextualLink."
                                .into(),
                        ));
                    }
                    _ => {
                        return Err(CausalityError(format!(
                            "evaluate_mixed_propagation (All) encountered an unhandled effect: {res_effect:?}"
                        )));
                    }
                }
            }
            if all_true {
                Ok(last_successful_effect)
            } else {
                Ok(PropagatingEffect::Deterministic(false))
            }
        }
        AggregateLogic::Any => {
            let mut any_true = false;
            let mut last_successful_effect = PropagatingEffect::Deterministic(false);
            let mut final_failure_effect: Option<PropagatingEffect> = None;

            for res_effect in resolved_effects {
                match res_effect {
                    PropagatingEffect::Deterministic(true) => {
                        any_true = true;
                        last_successful_effect = res_effect;
                    }
                    PropagatingEffect::Probabilistic(p) => {
                        if p > 0.0 {
                            any_true = true;
                            last_successful_effect = res_effect;
                        } else {
                            // If a probabilistic 0.0 is encountered, it's a potential failure
                            if final_failure_effect.is_none() {
                                final_failure_effect =
                                    Some(PropagatingEffect::Probabilistic(0.0));
                            }
                        }
                    }
                    PropagatingEffect::Deterministic(false) => {
                        // If a deterministic false is encountered, it's a potential failure
                        if final_failure_effect.is_none()
                            || matches!(
                                final_failure_effect,
                                Some(PropagatingEffect::Probabilistic(_))
                            )
                        {
                            final_failure_effect =
                                Some(PropagatingEffect::Deterministic(false));
                        }
                    }
                    PropagatingEffect::Halting => {
                        return Ok(PropagatingEffect::Halting);
                    }
                    PropagatingEffect::ContextualLink(_, _) => {
                        return Err(CausalityError(
                            "evaluate_mixed_propagation (Any) encountered a ContextualLink."
                                .into(),
                        ));
                    }
                    _ => {
                        return Err(CausalityError(format!(
                            "evaluate_mixed_propagation (Any) encountered an unhandled effect: {res_effect:?}"
                        )));
                    }
                }
            }
            if any_true {
                Ok(last_successful_effect)
            } else if let Some(failure_effect) = final_failure_effect {
                Ok(failure_effect)
            } else {
                Ok(PropagatingEffect::Deterministic(false)) // Default if no true and no specific failure effect
            }
        }
        AggregateLogic::None => {
            let mut any_true = false;
            let mut final_failure_effect: Option<PropagatingEffect> = None;

            for res_effect in resolved_effects {
                match res_effect {
                    PropagatingEffect::Deterministic(true) => {
                        any_true = true;
                        final_failure_effect = Some(PropagatingEffect::Deterministic(false));
                        break;
                    }
                    PropagatingEffect::Probabilistic(p) => {
                        if p > 0.0 {
                            any_true = true;
                            final_failure_effect = Some(PropagatingEffect::Probabilistic(0.0));
                            break;
                        }
                    }
                    PropagatingEffect::Deterministic(false) => {}
                    PropagatingEffect::Halting => {
                        return Ok(PropagatingEffect::Halting);
                    }
                    PropagatingEffect::ContextualLink(_, _) => {
                        return Err(CausalityError(
                            "evaluate_mixed_propagation (None) encountered a ContextualLink."
                                .into(),
                        ));
                    }
                    _ => {
                        return Err(CausalityError(format!(
                            "evaluate_mixed_propagation (None) encountered an unhandled effect: {res_effect:?}"
                        )));
                    }
                }
            }
            if any_true {
                if let Some(failure_effect) = final_failure_effect {
                    Ok(failure_effect)
                } else {
                    Ok(PropagatingEffect::Deterministic(false)) // Should not happen if any_true is true
                }
            } else {
                Ok(PropagatingEffect::Deterministic(true))
            }
        }
        AggregateLogic::Some(k) => {
            let mut success_count = 0;
            let mut last_successful_effect = PropagatingEffect::Deterministic(false);
            let mut final_failure_effect: Option<PropagatingEffect> = None;

            for res_effect in resolved_effects {
                match res_effect {
                    PropagatingEffect::Deterministic(true) => {
                        success_count += 1;
                        last_successful_effect = res_effect;
                    }
                    PropagatingEffect::Probabilistic(p) => {
                        if p > 0.0 {
                            success_count += 1;
                            last_successful_effect = res_effect;
                        }
                    }
                    PropagatingEffect::Deterministic(false) => {
                        if final_failure_effect.is_none()
                            || matches!(
                                final_failure_effect,
                                Some(PropagatingEffect::Probabilistic(_))
                            )
                        {
                            // Prioritize deterministic false over probabilistic 0.0
                            final_failure_effect =
                                Some(PropagatingEffect::Deterministic(false));
                        }
                    }
                    PropagatingEffect::Halting => {
                        return Ok(PropagatingEffect::Halting);
                    }
                    PropagatingEffect::ContextualLink(_, _) => {
                        return Err(CausalityError(
                            "evaluate_mixed_propagation (Some) encountered a ContextualLink."
                                .into(),
                        ));
                    }
                    _ => {
                        return Err(CausalityError(format!(
                            "evaluate_mixed_propagation (Some) encountered an unhandled effect: {res_effect:?}"
                        )));
                    }
                }
            }
            if success_count >= *k {
                Ok(last_successful_effect)
            } else if let Some(failure_effect) = final_failure_effect {
                Ok(failure_effect)
            } else {
                Ok(PropagatingEffect::Deterministic(false)) // Default if not enough successes and no specific failure effect
            }
        }
    }
}
RelayTo Resolution

The RelayTo effect resolution logic appears in multiple methods with similar patterns. The target causaloid lookup and evaluation should be verified to ensure it handles missing targets correctly and doesn't create infinite loops or circular dependencies.

let resolved_effect = match current_effect {
    PropagatingEffect::RelayTo(target_id, inner_effect) => {
        let target_causaloid = self
            .get_item_by_id(target_id as IdentificationValue)
            .ok_or_else(|| {
                CausalityError(format!(
                    "Relay target causaloid with ID {target_id} not found."
                ))
            })?;
        target_causaloid.evaluate(&inner_effect)?

Copy link
Contributor

qodo-merge-pro bot commented Jul 30, 2025

PR Code Suggestions ✨

Latest suggestions up to a8fd18c

CategorySuggestion                                                                                                                                    Impact
Incremental [*]
Implement unused aggregation logic parameter

The _logic parameter is prefixed with underscore but never used in the
implementation. Consider either implementing the aggregation logic or removing
the parameter if it's not needed for this method.

deep_causality/src/traits/causable/causable_reasoning.rs [56-60]

 fn evaluate_deterministic_propagation(
     &self,
     effect: &PropagatingEffect,
-    _logic: &AggregateLogic,
+    logic: &AggregateLogic,
 ) -> Result<PropagatingEffect, CausalityError> {
+    // TODO: Implement aggregation logic based on the logic parameter
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that the newly added _logic parameter is unused across multiple functions, which indicates incomplete implementation and is a valid concern for code review.

Medium
Replace unsafe unwrap with safe pattern matching

Replace the pattern of checking is_none() followed by unwrap() with a more
idiomatic approach using pattern matching or ok_or to avoid potential panic and
improve code clarity.

deep_causality/src/traits/transferable/mod.rs [38-42]

-if self.get_assumptions().is_none() {
-    return Err(AssumptionError::NoAssumptionsDefined);
-}
+let assumptions = match self.get_assumptions().as_ref() {
+    Some(assumptions) => assumptions,
+    None => return Err(AssumptionError::NoAssumptionsDefined),
+};
 
-let assumptions = self.get_assumptions().as_ref().unwrap();
-
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a non-idiomatic use of is_none() followed by unwrap() and proposes a safer, more idiomatic pattern matching approach that improves code clarity and robustness.

Low
Fix documentation comment spacing

Add consistent spacing after the triple slashes in documentation comments to
match Rust documentation conventions and maintain consistency with other
variants in the enum.

deep_causality/src/errors/assumption_error.rs [16-19]

-///Error returned when verification is attempted without data i.e. empty collection.
+/// Error returned when verification is attempted without data i.e. empty collection.
 NoDataToTestDefined,
-///Error to capture the specific failed assumption
+/// Error to capture the specific failed assumption
 AssumptionFailed(String),
  • Apply / Chat
Suggestion importance[1-10]: 3

__

Why: The suggestion correctly identifies and fixes inconsistent spacing in documentation comments, which improves code style and readability.

Low
General
Add comprehensive empty collection testing

The test calls number_assumption_valid() on an empty collection which may cause
division by zero or unexpected behavior. Consider adding proper error handling
or validation for this edge case.

deep_causality/tests/extensions/assumable/assumable_vec_tests.rs [11-21]

 #[test]
 fn test_empty() {
     let col: Vec<Assumption> = vec![];
     assert_eq!(col.len(), 0);
 
     let res = col.percent_assumption_valid();
     assert!(res.is_err());
 
+    // Verify that number_assumption_valid handles empty collections correctly
     let res = col.number_assumption_valid();
     assert_eq!(res, 0.0);
+    
+    // Also test that other methods handle empty collections gracefully
+    assert!(!col.all_assumptions_tested());
+    assert!(!col.all_assumptions_valid());
 }
  • Apply / Chat
Suggestion importance[1-10]: 3

__

Why: The suggestion correctly points out that the test for empty collections could be more comprehensive, although the existing test already covers the most critical edge case.

Low
  • More

Previous suggestions

Suggestions up to commit bb80213
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix inconsistent Any logic behavior

The logic returns last_effect when has_true is true, but last_effect could be
either true or false depending on iteration order. This creates inconsistent
behavior where a true result might be overwritten by a later false result.

deep_causality/src/traits/causable/causable_reasoning.rs [105-132]

 AggregateLogic::Any => {
     let mut has_true = false;
-    let mut last_effect = PropagatingEffect::Halting; // Default to Halting if no true or false found
+    let mut last_true_effect = PropagatingEffect::Deterministic(false);
     for res_effect in resolved_effects {
         match res_effect {
             PropagatingEffect::Deterministic(true) => {
                 has_true = true;
-                last_effect = res_effect;
+                last_true_effect = res_effect;
             }
             PropagatingEffect::Deterministic(false) => {
-                last_effect = res_effect;
+                // Continue without updating last_true_effect
             }
             PropagatingEffect::Halting => {
                 return Ok(PropagatingEffect::Halting);
             }
             _ => {
                 return Err(CausalityError(format!(
                     "evaluate_deterministic_propagation (Any) encountered a non-deterministic effect: {res_effect:?}. Only Deterministic effects are allowed."
                 )));
             }
         }
     }
     if has_true {
-        Ok(last_effect)
+        Ok(last_true_effect)
     } else {
         Ok(PropagatingEffect::Deterministic(false))
     }
 }
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a logic bug where a true result could be incorrectly overwritten by a subsequent false result, and the proposed fix correctly ensures the first true effect is returned.

Medium
General
Add validation for Some aggregation logic

The Some(k) logic should validate that k is not greater than the total number of
effects to prevent impossible conditions. An early validation would improve
robustness and provide clearer error messages.

deep_causality/src/traits/causable/causable_reasoning.rs [161-185]

 AggregateLogic::Some(k) => {
+    if *k > resolved_effects.len() {
+        return Err(CausalityError(format!(
+            "Some({k}) requires more effects than available ({} effects)",
+            resolved_effects.len()
+        )));
+    }
     let mut true_count = 0;
     for res_effect in resolved_effects {
         match res_effect {
             PropagatingEffect::Deterministic(true) => {
                 true_count += 1;
                 continue;
             }
             PropagatingEffect::Deterministic(false) => {}
             PropagatingEffect::Halting => {
                 return Ok(PropagatingEffect::Halting);
             }
             _ => {
                 return Err(CausalityError(format!(
                     "evaluate_deterministic_propagation (Some) encountered a non-deterministic effect: {res_effect:?}. Only Deterministic effects are allowed."
                 )));
             }
         }
     }
     if true_count >= *k {
         Ok(PropagatingEffect::Deterministic(true))
     } else {
         Ok(PropagatingEffect::Deterministic(false))
     }
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly proposes adding a validation check to handle cases where k is impossible to satisfy, which improves robustness and provides clearer error feedback.

Medium
Incremental [*]
Use pattern matching assertion

Use assert_matches! macro or pattern matching in the assertion to make the error
checking more concise and avoid the intermediate variable assignment.

deep_causality/tests/types/model_types/model/model_tests.rs [190-195]

 // AssumptionError::NoDataToTestDefined
 let res = model.verify_assumptions(&[]);
-assert!(res.is_err());
+assert!(matches!(res, Err(AssumptionError::NoDataToTestDefined)));
 
-let err = res.unwrap_err();
-assert_eq!(err, AssumptionError::NoDataToTestDefined);
-
Suggestion importance[1-10]: 6

__

Why: This is a good suggestion that uses the idiomatic matches! macro to make the error check more concise and robust by combining multiple assertions into one.

Low
Use Display trait directly

Consider using Display trait directly instead of format! macro for simple string
comparisons to improve performance and readability.

deep_causality/tests/types/reasoning_types/aggregate_logic_tests.rs [8-12]

 #[test]
 fn test_aggregate_logic_all() {
     let logic = AggregateLogic::All;
-    assert_eq!(format!("{logic}"), "All");
+    assert_eq!(logic.to_string(), "All");
 }
Suggestion importance[1-10]: 2

__

Why: The suggestion offers a minor stylistic alternative that might be slightly more performant, but the impact on readability and performance is negligible in a test.

Low
Suggestions up to commit 8e6920e
CategorySuggestion                                                                                                                                    Impact
General
Fix probabilistic aggregation logic inconsistency

The Any logic returns the first positive probability found, but this may not
represent the intended aggregation behavior. Consider returning the highest
probability or implementing a proper probabilistic union operation.

deep_causality/src/traits/causable/causable_reasoning.rs [260-283]

 AggregateLogic::Any => {
+    let mut max_probability = 0.0;
+    let mut found_deterministic_true = false;
+    
     for res_effect in resolved_effects {
         match res_effect {
             PropagatingEffect::Probabilistic(p) => {
-                if p > 0.0 {
-                    return Ok(PropagatingEffect::Probabilistic(p));
+                if p > max_probability {
+                    max_probability = p;
                 }
             }
             PropagatingEffect::Deterministic(true) => {
-                return Ok(PropagatingEffect::Deterministic(true));
+                found_deterministic_true = true;
             }
             PropagatingEffect::Deterministic(false) => {}
             PropagatingEffect::Halting => {
                 return Ok(PropagatingEffect::Halting);
             }
             _ => {
                 return Err(CausalityError(format!(
                     "evaluate_probabilistic_propagation (Any) encountered an unhandled effect: {res_effect:?}"
                 )));
             }
         }
     }
-    Ok(PropagatingEffect::Probabilistic(0.0))
+    
+    if found_deterministic_true {
+        Ok(PropagatingEffect::Deterministic(true))
+    } else if max_probability > 0.0 {
+        Ok(PropagatingEffect::Probabilistic(max_probability))
+    } else {
+        Ok(PropagatingEffect::Probabilistic(0.0))
+    }
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that returning the first positive probability for AggregateLogic::Any is arbitrary and proposes a more robust alternative of returning the maximum probability, which better reflects the intent of the logic.

Medium
Possible issue
Prevent integer overflow in counter logic

The Some(k) logic has potential integer overflow when success_count approaches
usize::MAX. Add bounds checking to prevent overflow and ensure k is within
reasonable limits.

deep_causality/src/traits/causable/causable_reasoning.rs [308-339]

 AggregateLogic::Some(k) => {
-    let mut success_count = 0;
+    if *k == 0 {
+        return Ok(PropagatingEffect::Deterministic(true));
+    }
+    
+    let mut success_count = 0usize;
     let mut last_successful_effect = PropagatingEffect::Probabilistic(0.0);
     for res_effect in resolved_effects {
         match res_effect {
             PropagatingEffect::Probabilistic(p) => {
                 if p > 0.0 {
-                    success_count += 1;
+                    success_count = success_count.saturating_add(1);
                     last_successful_effect = res_effect;
+                    if success_count >= *k {
+                        return Ok(last_successful_effect);
+                    }
                 }
             }
             PropagatingEffect::Deterministic(true) => {
-                success_count += 1;
+                success_count = success_count.saturating_add(1);
                 last_successful_effect = res_effect;
+                if success_count >= *k {
+                    return Ok(last_successful_effect);
+                }
             }
             PropagatingEffect::Deterministic(false) => {}
             PropagatingEffect::Halting => {
                 return Ok(PropagatingEffect::Halting);
             }
             _ => {
                 return Err(CausalityError(format!(
                     "evaluate_probabilistic_propagation (Some) encountered an unhandled effect: {res_effect:?}"
                 )));
             }
         }
     }
-    if success_count >= *k {
-        Ok(last_successful_effect)
-    } else {
-        Ok(PropagatingEffect::Probabilistic(0.0))
-    }
+    Ok(PropagatingEffect::Probabilistic(0.0))
 }
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out a theoretical integer overflow risk with success_count and suggests using saturating_add, which is a valid improvement for robustness, although the risk is negligible in practice.

Low

@marvin-hansen marvin-hansen merged commit b485102 into deepcausality-rs:main Jul 31, 2025
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enable Programmatic Verification of Model Assumptions
1 participant