diff --git a/Cargo.toml b/Cargo.toml index e7539b1..cfd16ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ tokio-rustls = "0.24" bellande_step = "0.1.1" bellande_limit = "0.1.2" bellande_node_importance = "0.1.3" +bellande_probability = "0.1.2" bellande_particle = "0.1.1" -bellande_probability = "0.1.1" # bellande_tree = "0.1.0" # bellande_segment = "0.1.0" diff --git a/README.md b/README.md index 12f79d6..ee17c2e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Bellande Mesh Sync {BMS} +## Run Bellande Mesh Sync with Bellande API + - Use the Bellande Mesh Sync with the Bellande API to do efficient communication + # Run Bellos Scripts - build_bellande_framework.bellos - make_rust_executable.bellos diff --git a/src/algorithm/bellande_probability.rs b/src/algorithm/bellande_probability.rs index 1e1074c..d44de13 100644 --- a/src/algorithm/bellande_probability.rs +++ b/src/algorithm/bellande_probability.rs @@ -14,4 +14,317 @@ // along with this program. If not, see . use crate::algorithm::connections::BellandeArchError; -// use bellande_probability::make_bellande_probability_request; +use bellande_probability::make_bellande_probability_request; +use futures::future::join_all; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +/// Configuration for spatial probability calculations using Bellande Probability algorithm +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SpatialProbabilityConfig { + pub mu_function: String, + pub sigma_function: String, + pub input_vector: Vec, + pub dimensions: i32, + pub full_auth: bool, + pub use_local_executable: bool, +} + +/// Result of a spatial probability calculation using Bellande Probability +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ProbabilityCalculationResult { + pub probability_values: Vec, + pub confidence_intervals: Option>>, + pub execution_time_ms: Option, + pub statistical_metrics: Option, +} + +/// Extended configuration for advanced Bellande Probability calculations +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AdvancedProbabilityConfig { + pub basic_config: SpatialProbabilityConfig, + pub distribution_parameters: Option, + pub confidence_level: Option, + pub prior_probabilities: Option>, +} + +/// Main spatial probability module that leverages the Bellande Probability algorithm +pub struct SpatialProbabilityCalculator; + +impl SpatialProbabilityCalculator { + /// Performs a spatial probability calculation using the Bellande Probability algorithm + pub async fn calculate( + config: SpatialProbabilityConfig, + ) -> Result { + // Validate input parameters + if config.mu_function.is_empty() || config.sigma_function.is_empty() { + return Err(BellandeArchError::InvalidParameters( + "Mu and Sigma functions cannot be empty".to_string(), + )); + } + + if config.input_vector.is_empty() { + return Err(BellandeArchError::InvalidParameters( + "Input vector cannot be empty".to_string(), + )); + } + + if config.dimensions <= 0 { + return Err(BellandeArchError::InvalidParameters( + "Dimensions must be a positive integer".to_string(), + )); + } + + // Convert input vector to JSON Value + let x = serde_json::json!(config.input_vector); + + // Run the Bellande Probability algorithm using the API + let result = if !config.use_local_executable { + make_bellande_probability_request( + config.mu_function.clone(), + config.sigma_function.clone(), + x, + config.dimensions, + config.full_auth, + ) + .await + .map_err(|e| BellandeArchError::AlgorithmError(e.to_string()))? + } else { + return Err(BellandeArchError::AlgorithmError( + "Local executable functionality not implemented".to_string(), + )); + }; + + // Process and transform the results + Self::process_result(result) + } + + /// Process the raw Bellande Probability result into our domain-specific format + pub fn process_result( + result: Value, + ) -> Result { + // Extract the probability values from the result + let probability_values = result + .get("probability_values") + .and_then(|p| p.as_array()) + .ok_or_else(|| { + BellandeArchError::AlgorithmError( + "Missing probability values in algorithm result".to_string(), + ) + })? + .iter() + .map(|v| { + v.as_f64().ok_or_else(|| { + BellandeArchError::AlgorithmError( + "Non-numeric probability value in algorithm result".to_string(), + ) + }) + }) + .collect::, _>>()?; + + // Extract confidence intervals if available + let confidence_intervals = result + .get("confidence_intervals") + .and_then(|ci| ci.as_array()) + .map(|intervals| { + intervals + .iter() + .filter_map(|interval| { + interval.as_array().map(|bounds| { + bounds + .iter() + .filter_map(|v| v.as_f64()) + .collect::>() + }) + }) + .collect::>>() + }); + + // Extract execution time if available + let execution_time_ms = result + .get("execution_time_ms") + .and_then(|t| t.as_i64()) + .map(|t| t as i32); + + // Extract statistical metrics if available + let statistical_metrics = result.get("statistical_metrics").cloned(); + + Ok(ProbabilityCalculationResult { + probability_values, + confidence_intervals, + execution_time_ms, + statistical_metrics, + }) + } + + /// Calculates the probability distribution for a given set of inputs + pub async fn calculate_distribution( + mu_function: String, + sigma_function: String, + inputs: Vec>, + dimensions: i32, + ) -> Result, BellandeArchError> { + // Validate inputs + if inputs.is_empty() { + return Err(BellandeArchError::InvalidParameters( + "Input vectors cannot be empty".to_string(), + )); + } + + if mu_function.is_empty() || sigma_function.is_empty() { + return Err(BellandeArchError::InvalidParameters( + "Mu and Sigma functions cannot be empty".to_string(), + )); + } + + // Calculate probabilities for each input vector + let mut probabilities = Vec::new(); + for input_vector in inputs { + // Basic configuration + let config = SpatialProbabilityConfig { + mu_function: mu_function.clone(), + sigma_function: sigma_function.clone(), + input_vector, + dimensions, + full_auth: false, + use_local_executable: false, + }; + + // Perform calculation + let result = Self::calculate(config).await?; + + // Add the first probability value (assuming it's the main one) + if !result.probability_values.is_empty() { + probabilities.push(result.probability_values[0]); + } else { + return Err(BellandeArchError::AlgorithmError( + "Empty probability values returned".to_string(), + )); + } + } + + Ok(probabilities) + } + + /// Batch processes multiple probability calculations in parallel + pub async fn batch_calculate( + configs: Vec, + ) -> Vec> { + // Process each configuration in parallel + let futures = configs.into_iter().map(|config| Self::calculate(config)); + + // Collect all results + join_all(futures).await + } + + /// Advanced batch processing with custom parameters for each calculation + pub async fn advanced_batch_calculate( + configs: Vec, + ) -> Vec> { + let futures = configs.into_iter().map(|config| { + // Use basic calculation + Self::calculate(config.basic_config) + }); + + join_all(futures).await + } + + /// Normalizes a set of probability values to ensure they sum to 1.0 + pub fn normalize_probabilities(probabilities: &[f64]) -> Result, BellandeArchError> { + if probabilities.is_empty() { + return Err(BellandeArchError::InvalidParameters( + "Probability vector cannot be empty for normalization".to_string(), + )); + } + + // Calculate sum of all probabilities + let sum = probabilities.iter().sum::(); + + // Check if sum is close to zero to avoid division by zero + if sum.abs() < 1e-10 { + return Err(BellandeArchError::AlgorithmError( + "Cannot normalize probabilities with sum close to zero".to_string(), + )); + } + + // Normalize each probability by dividing by the sum + let normalized = probabilities.iter().map(|&p| p / sum).collect(); + + Ok(normalized) + } + + /// Calculates the weighted average of vectors using probability weights + pub fn calculate_weighted_average( + vectors: &[Vec], + probabilities: &[f64], + ) -> Result, BellandeArchError> { + if vectors.is_empty() || probabilities.is_empty() { + return Err(BellandeArchError::InvalidParameters( + "Vectors and probabilities cannot be empty for weighted average".to_string(), + )); + } + + if vectors.len() != probabilities.len() { + return Err(BellandeArchError::DimensionMismatch(format!( + "Mismatch between number of vectors ({}) and probabilities ({})", + vectors.len(), + probabilities.len() + ))); + } + + // Ensure all vectors have the same dimension + let dimension = vectors[0].len(); + for (i, vector) in vectors.iter().enumerate() { + if vector.len() != dimension { + return Err(BellandeArchError::DimensionMismatch(format!( + "Vector at index {} has dimension {} but expected {}", + i, + vector.len(), + dimension + ))); + } + } + + // Calculate the weighted average for each dimension + let mut weighted_average = vec![0.0; dimension]; + let prob_sum = probabilities.iter().sum::(); + + if prob_sum.abs() < 1e-10 { + return Err(BellandeArchError::AlgorithmError( + "Sum of probabilities is too close to zero".to_string(), + )); + } + + for d in 0..dimension { + for (i, vector) in vectors.iter().enumerate() { + weighted_average[d] += vector[d] * probabilities[i] / prob_sum; + } + } + + Ok(weighted_average) + } +} + +/// Converts a probability value to a log-likelihood for numerical stability +pub fn probability_to_log_likelihood(probability: f64) -> Result { + if probability <= 0.0 || probability > 1.0 { + return Err(BellandeArchError::InvalidParameters(format!( + "Invalid probability value: {}", + probability + ))); + } + + Ok(probability.ln()) +} + +/// Converts a log-likelihood back to a probability value +pub fn log_likelihood_to_probability(log_likelihood: f64) -> Result { + if log_likelihood > 0.0 { + return Err(BellandeArchError::InvalidParameters(format!( + "Invalid log likelihood value: {}", + log_likelihood + ))); + } + + Ok(log_likelihood.exp()) +} diff --git a/src/algorithm/connections.rs b/src/algorithm/connections.rs index 97e7c1e..c075ae2 100644 --- a/src/algorithm/connections.rs +++ b/src/algorithm/connections.rs @@ -19,6 +19,7 @@ use std::fmt; #[derive(Debug)] pub enum BellandeArchError { DimensionMismatch(String), + InvalidParameters(String), InvalidCoordinates(String), AlgorithmError(String), NetworkError(String), @@ -28,6 +29,7 @@ impl fmt::Display for BellandeArchError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { BellandeArchError::DimensionMismatch(msg) => write!(f, "Dimension mismatch: {}", msg), + BellandeArchError::InvalidParameters(msg) => write!(f, "Invalid parameters: {}", msg), BellandeArchError::InvalidCoordinates(msg) => write!(f, "Invalid coordinates: {}", msg), BellandeArchError::AlgorithmError(msg) => write!(f, "Algorithm error: {}", msg), BellandeArchError::NetworkError(msg) => write!(f, "Network error: {}", msg),