Skip to content
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

Added & implemented Indexable trait for Context; #125

Merged
merged 5 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deep_causality/examples/ctx/src/io/file/parquet_2_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

use chrono::{DateTime, TimeZone, Utc};
use parquet::record::{Row, RowAccessor};
use rust_decimal::Decimal;
use rust_decimal::prelude::FromPrimitive;
use rust_decimal::Decimal;

use crate::types::data_symbol::DataSymbol;
use crate::types::date_time_bar::DateTimeBar;
Expand Down
2 changes: 1 addition & 1 deletion deep_causality/examples/ctx/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use deep_causality::prelude::{ContextuableGraph, Identifiable, TimeScale};

use crate::model::get_main_causaloid;
use crate::utils;
use crate::workflow::{build_time_data_context, load_data};
use crate::workflow::build_model::build_model;
use crate::workflow::{build_time_data_context, load_data};

pub fn run() {
// Determines the maximum level of time resolution in the context hypergraph.
Expand Down
2 changes: 2 additions & 0 deletions deep_causality/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub use crate::protocols::contextuable_graph::ContextuableGraph;
pub use crate::protocols::contextuable_graph::ExtendableContextuableGraph;
// Identifiable protocol
pub use crate::protocols::identifiable::Identifiable;
// Indexable protocol
pub use crate::protocols::indexable::Indexable;
// Inferable protocol
pub use crate::protocols::inferable::Inferable;
pub use crate::protocols::inferable::InferableReasoning;
Expand Down
102 changes: 101 additions & 1 deletion deep_causality/src/protocols/assumable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,24 @@

use crate::prelude::{DescriptionValue, EvalFn, Identifiable, NumericalValue};

/// The Assumable trait defines the interface for objects that represent
/// assumptions that can be tested and verified. Assumable types must also
/// implement Identifiable.
///
/// # Trait Methods
///
/// * `description` - Returns a description of the assumption as a
/// DescriptionValue
/// * `assumption_fn` - Returns the function that will evaluate the assumption
/// as an EvalFn
/// * `assumption_tested` - Returns whether this assumption has been tested
/// * `assumption_valid` - Returns whether this assumption is valid
/// * `verify_assumption` - Tests the assumption against the provided data and
/// returns whether it is valid
///
/// The AssumableReasoning trait provides default implementations for common
/// operations over collections of Assumable types.
///
pub trait Assumable: Identifiable {
fn description(&self) -> DescriptionValue;
fn assumption_fn(&self) -> EvalFn;
Expand All @@ -11,6 +29,29 @@ pub trait Assumable: Identifiable {
fn verify_assumption(&self, data: &[NumericalValue]) -> bool;
}

/// The AssumableReasoning trait provides default implementations for common
/// operations over collections of Assumable types.
///
/// It requires the associated type T to implement Assumable.
///
/// # Trait Methods
///
/// * `len` - Returns the number of items in the collection.
/// * `is_empty` - Returns true if the collection is empty.
/// * `get_all_items` - Returns a vector containing references to all items.
///
/// It also provides default implementations for:
///
/// * `all_assumptions_tested` - Checks if all assumptions have been tested.
/// * `all_assumptions_valid` - Checks if all assumptions are valid.
/// * `number_assumption_valid` - Returns the number of valid assumptions.
/// * `percent_assumption_valid` - Returns the percentage of valid assumptions.
/// * `verify_all_assumptions` - Verifies all assumptions against provided data.
/// * `get_all_invalid_assumptions` - Filters for invalid assumptions.
/// * `get_all_valid_assumptions` - Filters for valid assumptions.
/// * `get_all_tested_assumptions` - Filters for tested assumptions.
/// * `get_all_untested_assumptions` - Filters for untested assumptions.
///
pub trait AssumableReasoning<T>
where
T: Assumable,
Expand All @@ -20,8 +61,18 @@ where
fn is_empty(&self) -> bool;
fn get_all_items(&self) -> Vec<&T>;

// Default implementations for all other methods below.
//
// Default implementations for all other trait methods.
//

/// Checks if all assumptions in the collection have been tested.
///
/// Iterates through all items returned by `get_all_items()` and checks if
/// `assumption_tested()` returns true for each one.
///
/// Returns false if any assumption has not been tested, otherwise returns
/// true.
///
fn all_assumptions_tested(&self) -> bool {
for elem in self.get_all_items() {
if !elem.assumption_tested() {
Expand All @@ -31,6 +82,13 @@ where
true
}

/// Checks if all assumptions in the collection are valid.
///
/// Iterates through all items returned by `get_all_items()` and checks if
/// `assumption_valid()` returns true for each one.
///
/// Returns false if any assumption is invalid, otherwise returns true.
///
fn all_assumptions_valid(&self) -> bool {
for a in self.get_all_items() {
if !a.assumption_valid() {
Expand All @@ -40,44 +98,86 @@ where
true
}

/// Returns the number of valid assumptions in the collection.
///
/// Gets all items via `get_all_items()`, filters to keep only those where
/// `assumption_valid()` returns true, and returns the count as a
/// NumericalValue.
///
fn number_assumption_valid(&self) -> NumericalValue {
self.get_all_items()
.iter()
.filter(|a| a.assumption_valid())
.count() as NumericalValue
}

/// Returns the percentage of valid assumptions in the collection.
///
/// Calculates the percentage by dividing the number of valid assumptions
/// (from `number_assumption_valid()`) by the total number of assumptions
/// (from `len()`) and multiplying by 100.
///
/// Returns the percentage as a NumericalValue.
///
fn percent_assumption_valid(&self) -> NumericalValue {
(self.number_assumption_valid() / self.len() as NumericalValue) * 100.0
}

/// Verifies all assumptions in the collection against the provided data.
///
/// Iterates through all items returned by `get_all_items()` and calls
/// `verify_assumption()` on each one, passing the `data`.
///
/// This will test each assumption against the data and update the
/// `assumption_valid` and `assumption_tested` flags accordingly.
///
fn verify_all_assumptions(&self, data: &[NumericalValue]) {
for a in self.get_all_items() {
a.verify_assumption(data);
}
}

/// Returns a vector containing references to all invalid assumptions.
///
/// Gets all items via `get_all_items()`, filters to keep only those where
/// `assumption_valid()` returns false, and collects into a vector.
///
fn get_all_invalid_assumptions(&self) -> Vec<&T> {
self.get_all_items()
.into_iter()
.filter(|a| !a.assumption_valid())
.collect()
}

/// Returns a vector containing references to all valid assumptions.
///
/// Gets all items via `get_all_items()`, filters to keep only those where
/// `assumption_valid()` returns true, and collects into a vector.
///
fn get_all_valid_assumptions(&self) -> Vec<&T> {
self.get_all_items()
.into_iter()
.filter(|a| a.assumption_valid())
.collect()
}

/// Returns a vector containing references to all tested assumptions.
///
/// Gets all items via `get_all_items()`, filters to keep only those where
/// `assumption_tested()` returns true, and collects into a vector.
///
fn get_all_tested_assumptions(&self) -> Vec<&T> {
self.get_all_items()
.into_iter()
.filter(|a| a.assumption_tested())
.collect()
}

/// Returns a vector containing references to all untested assumptions.
///
/// Gets all items via `get_all_items()`, filters to keep only those where
/// `assumption_tested()` returns false, and collects into a vector.
///
fn get_all_untested_assumptions(&self) -> Vec<&T> {
self.get_all_items()
.into_iter()
Expand Down
87 changes: 86 additions & 1 deletion deep_causality/src/protocols/causable/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ use std::collections::HashMap;
use crate::errors::CausalityError;
use crate::prelude::{Identifiable, IdentificationValue, NumericalValue};

/// The Causable trait defines the core behavior for causal reasoning.
///
/// It requires implementing the Identifiable trait.
///
/// # Trait Methods
///
/// * `explain` - Returns an explanation of the cause as a String.
/// * `is_active` - Returns true if this cause is currently active.
/// * `is_singleton` - Returns true if this cause acts on a single data point.
/// * `verify_single_cause` - Verifies this cause against a single data point.
/// * `verify_all_causes` - Verifies this cause against multiple data points.
///
/// `verify_single_cause` and `verify_all_causes` return a Result indicating
/// if the cause was validated or not.
///
pub trait Causable: Identifiable {
fn explain(&self) -> Result<String, CausalityError>;
fn is_active(&self) -> bool;
Expand All @@ -20,6 +35,24 @@ pub trait Causable: Identifiable {
) -> Result<bool, CausalityError>;
}

/// The CausableReasoning trait provides default implementations for reasoning over collections of Causable items.
///
/// It requires the generic type T to implement the Causable trait.
///
/// The trait provides default methods for:
///
/// - Getting active/inactive causes
/// - Counting active causes
/// - Calculating percentage of active causes
/// - Explaining all causes
/// - Verifying causes against data
///
/// The `reason_all_causes` method verifies all causes in the collection against the provided data,
/// using the cause's `is_singleton` method to determine whether to call `verify_single_cause` or
/// `verify_all_causes`.
///
/// An index is emulated for the data to enable singleton cause verification.
///
pub trait CausableReasoning<T>
where
T: Causable,
Expand All @@ -30,8 +63,17 @@ where
fn to_vec(&self) -> Vec<T>;
fn get_all_items(&self) -> Vec<&T>;

//
// Default implementations for all other methods are provided below.

//

/// Checks if all causes in the collection are active.
///
/// Iterates through all causes via `get_all_items()` and returns false
/// if any cause's `is_active()` method returns false.
///
/// If all causes are active, returns true.
///
fn get_all_causes_true(&self) -> bool {
for cause in self.get_all_items() {
if !cause.is_active() {
Expand All @@ -42,33 +84,69 @@ where
true
}

/// Returns a vector containing references to all active causes.
///
/// Gets all causes via `get_all_items()`, filters to keep only those where
/// `is_active()` returns true, and collects into a vector.
///
fn get_all_active_causes(&self) -> Vec<&T> {
self.get_all_items()
.into_iter()
.filter(|cause| cause.is_active())
.collect()
}

/// Returns a vector containing references to all inactive causes.
///
/// Gets all causes via `get_all_items()`, filters to keep only those where
/// `is_active()` returns false, and collects into a vector.
///
fn get_all_inactive_causes(&self) -> Vec<&T> {
self.get_all_items()
.into_iter()
.filter(|cause| !cause.is_active())
.collect()
}

/// Returns the number of active causes.
///
/// Gets all causes via `get_all_items()`, filters to keep only active ones,
/// counts them, and returns the count as a NumericalValue.
///
fn number_active(&self) -> NumericalValue {
self.get_all_items()
.iter()
.filter(|c| c.is_active())
.count() as NumericalValue
}

/// Calculates the percentage of active causes.
///
/// Gets the number of active causes via `number_active()`.
/// Gets the total number of causes via `len()`.
/// Divides the active count by the total.
/// Multiplies by 100 to get a percentage.
/// Returns the result as a NumericalValue.
///
fn percent_active(&self) -> NumericalValue {
let count = self.number_active();
let total = self.len() as NumericalValue;
(count / total) * (100 as NumericalValue)
}

/// Verifies all causes in the collection against the provided data.
///
/// Returns an error if the collection is empty.
///
/// Iterates through all causes, using the cause's `is_singleton()` method
/// to determine whether to call `verify_single_cause()` or `verify_all_causes()`.
///
/// For singleton causes, the data index is emulated to enable lookup by index.
///
/// If any cause fails verification, returns Ok(false).
///
/// If all causes pass verification, returns Ok(true).
///
fn reason_all_causes(&self, data: &[NumericalValue]) -> Result<bool, CausalityError> {
if self.is_empty() {
return Err(CausalityError("Causality collection is empty".into()));
Expand Down Expand Up @@ -97,6 +175,13 @@ where
Ok(true)
}

/// Generates an explanation by concatenating the explain() text of all causes.
///
/// Calls explain() on each cause and unwraps the result.
/// Concatenates the explanations by inserting newlines between each one.
///
/// Returns the concatenated explanation string.
///
fn explain(&self) -> String {
let mut explanation = String::new();
for cause in self.get_all_items() {
Expand Down
Loading
Loading