From 19f4a5acae5a0e62c8a120e8694ad43f05ec44a4 Mon Sep 17 00:00:00 2001 From: RonaldsonBellande Date: Mon, 14 Apr 2025 18:16:40 -0400 Subject: [PATCH] update --- Cargo.toml | 5 +- src/algorithm/bellande_search_path.rs | 14 - src/algorithm/bellande_step.rs | 637 ++++++++++++++++++++++++++ src/algorithm/mod.rs | 1 - src/utilities/utilities.rs | 22 - 5 files changed, 641 insertions(+), 38 deletions(-) delete mode 100644 src/algorithm/bellande_search_path.rs diff --git a/Cargo.toml b/Cargo.toml index 986a382..08505b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,14 +22,17 @@ hyper = { version = "0.14", features = ["full"] } serde_json = "1.0" bincode = "1.3" rand = "0.8" +futures = "0.3" rustls = "0.21" tokio-util = { version = "0.7", features = ["full"] } async-trait = "0.1" tokio-rustls = "0.24" # Bellande Architecture -bellande_step = "0.1.0" +bellande_step = "0.1.1" bellande_limit = "0.1.0" bellande_node_importance = "0.1.0" bellande_particle = "0.1.1" bellande_probability = "0.1.1" +# bellande_tree = "0.1.0" +# bellande_segment = "0.1.0" diff --git a/src/algorithm/bellande_search_path.rs b/src/algorithm/bellande_search_path.rs deleted file mode 100644 index 3441dd2..0000000 --- a/src/algorithm/bellande_search_path.rs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (C) 2025 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . diff --git a/src/algorithm/bellande_step.rs b/src/algorithm/bellande_step.rs index 3441dd2..e2dd2e9 100644 --- a/src/algorithm/bellande_step.rs +++ b/src/algorithm/bellande_step.rs @@ -12,3 +12,640 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . + +use bellande_step::make_bellande_step_request; +use futures::future::join_all; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::fmt; + +/// Error types specific to this library +#[derive(Debug)] +pub enum BellandeArchError { + DimensionMismatch(String), + InvalidCoordinates(String), + AlgorithmError(String), + NetworkError(String), +} + +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::InvalidCoordinates(msg) => write!(f, "Invalid coordinates: {}", msg), + BellandeArchError::AlgorithmError(msg) => write!(f, "Algorithm error: {}", msg), + BellandeArchError::NetworkError(msg) => write!(f, "Network error: {}", msg), + } + } +} + +impl std::error::Error for BellandeArchError {} + +/// Configuration for spatial coordinate transformation +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SpatialTransformConfig { + pub start_coordinates: Vec, + pub end_coordinates: Vec, + pub iterations: i32, + pub use_local_executable: bool, +} + +/// Result of a spatial transformation operation +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TransformationResult { + pub path_coordinates: Vec>, + pub convergence_metrics: Option>, + pub execution_time_ms: Option, +} + +/// Extended configuration for advanced transformations +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AdvancedTransformConfig { + pub basic_config: SpatialTransformConfig, + pub obstacles: Option>>, + pub constraint_regions: Option>>>, + pub preferred_path_bias: Option, + pub smoothing_factor: Option, +} + +/// Configuration for coordinate system operations +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CoordinateSystemConfig { + pub source_system: String, + pub target_system: String, + pub dimension_map: Option>, +} + +/// Main spatial transformation module that leverages the Bellande Step algorithm +pub struct SpatialTransformer; + +impl SpatialTransformer { + /// Performs a spatial coordinate transformation using the Bellande Step algorithm + pub async fn transform( + config: SpatialTransformConfig, + ) -> Result { + // Validate input coordinates + if config.start_coordinates.is_empty() || config.end_coordinates.is_empty() { + return Err(BellandeArchError::InvalidCoordinates( + "Coordinate vectors cannot be empty".to_string(), + )); + } + let dimensions = config.start_coordinates.len(); + + // Verify that both coordinate sets have the same dimensions + if dimensions != config.end_coordinates.len() { + return Err(BellandeArchError::DimensionMismatch(format!( + "Start coordinates have {} dimensions but end coordinates have {}", + dimensions, + config.end_coordinates.len() + ))); + } + // Convert coordinates to JSON Values + let node0 = serde_json::json!(config.start_coordinates); + let node1 = serde_json::json!(config.end_coordinates); + + // Run the Bellande Step algorithm using the API + let result = if !config.use_local_executable { + make_bellande_step_request(node0, node1, config.iterations, dimensions as i32) + .await + .map_err(|e| BellandeArchError::AlgorithmError(e.to_string()))? + } else { + return Err(BellandeArchError::AlgorithmError( + "Start and end coordinates cannot be empty".to_string(), + )); + }; + + // Process and transform the results + Self::process_result(result) + } + + /// Process the raw Bellande Step result into our domain-specific format + fn process_result(result: Value) -> Result { + // Extract the path data from the result + let path = result + .get("path") + .and_then(|p| p.as_array()) + .ok_or_else(|| { + BellandeArchError::AlgorithmError( + "Missing path data in algorithm result".to_string(), + ) + })?; + + // Convert the path to our format + let mut path_coordinates = Vec::new(); + for point in path { + let coords = point + .as_array() + .ok_or_else(|| { + BellandeArchError::AlgorithmError( + "Invalid point format in algorithm result".to_string(), + ) + })? + .iter() + .map(|v| { + v.as_f64().ok_or_else(|| { + BellandeArchError::AlgorithmError( + "Non-numeric coordinate in algorithm result".to_string(), + ) + }) + }) + .collect::, _>>()?; + + path_coordinates.push(coords); + } + + // Extract any metrics if available + let convergence_metrics = result + .get("convergence_metrics") + .and_then(|m| m.as_array()) + .map(|metrics| { + metrics + .iter() + .filter_map(|v| v.as_f64()) + .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); + + Ok(TransformationResult { + path_coordinates, + convergence_metrics, + execution_time_ms, + }) + } + + /// Optimizes a path between two points using advanced parameters + pub async fn optimize_path( + start: Vec, + end: Vec, + obstacles: Vec>, + precision: f64, + ) -> Result { + // Validate inputs + if start.is_empty() || end.is_empty() { + return Err(BellandeArchError::InvalidCoordinates( + "Start and end coordinates cannot be empty".to_string(), + )); + } + + if start.len() != end.len() { + return Err(BellandeArchError::DimensionMismatch(format!( + "Start coordinates have {} dimensions but end coordinates have {}", + start.len(), + end.len() + ))); + } + + // Determine appropriate iteration count based on precision + let iterations = match precision { + p if p < 0.01 => 500, + p if p < 0.1 => 200, + _ => 100, + }; + + // Basic configuration + let config = SpatialTransformConfig { + start_coordinates: start.clone(), + end_coordinates: end.clone(), + iterations, + use_local_executable: false, + }; + + // Perform initial transformation + let initial_result = Self::transform(config.clone()).await?; + + // If there are obstacles, we need more sophisticated processing + if !obstacles.is_empty() { + return Self::optimize_path_with_obstacles(config, obstacles).await; + } + + Ok(initial_result) + } + + /// Advanced path optimization that handles obstacles + async fn optimize_path_with_obstacles( + config: SpatialTransformConfig, + obstacles: Vec>, + ) -> Result { + // Validate obstacle data + for obstacle in &obstacles { + if obstacle.len() != config.start_coordinates.len() { + return Err(BellandeArchError::DimensionMismatch(format!( + "Obstacle has {} dimensions but path has {} dimensions", + obstacle.len(), + config.start_coordinates.len() + ))); + } + } + + // Define a safety radius around obstacles + let safety_radius = 1.0; + + // Create a list of waypoints, starting with the start point + let mut waypoints = vec![config.start_coordinates.clone()]; + + // For each obstacle, determine if we need to route around it + for obstacle in &obstacles { + // Create a waypoint that avoids this obstacle + + // For simplicity, we'll just offset in the first dimension + let mut waypoint = obstacle.clone(); + waypoint[0] += safety_radius * 2.0; + + // Only add unique waypoints that aren't too close to existing ones + if !waypoints + .iter() + .any(|wp| euclidean_distance(wp, &waypoint).unwrap_or(f64::MAX) < safety_radius) + { + waypoints.push(waypoint); + } + } + + // Add the end point as final waypoint + waypoints.push(config.end_coordinates.clone()); + + // Now transform between each successive pair of waypoints + let mut segments = Vec::new(); + for i in 0..waypoints.len() - 1 { + let segment_config = SpatialTransformConfig { + start_coordinates: waypoints[i].clone(), + end_coordinates: waypoints[i + 1].clone(), + iterations: config.iterations / waypoints.len() as i32, + use_local_executable: config.use_local_executable, + }; + + let segment_result = Self::transform(segment_config).await?; + segments.push(segment_result); + } + + // Combine the segments into a single path + let mut combined_path = Vec::new(); + let mut combined_metrics = Vec::new(); + let mut total_time = 0; + + for (i, segment) in segments.iter().enumerate() { + if i == 0 { + // Include the entire first segment + combined_path.extend(segment.path_coordinates.clone()); + } else { + // For subsequent segments, skip the first point to avoid duplicates + combined_path.extend(segment.path_coordinates[1..].to_vec()); + } + + // Combine metrics if available + if let Some(metrics) = &segment.convergence_metrics { + combined_metrics.extend(metrics.clone()); + } + + // Add execution time + if let Some(time) = segment.execution_time_ms { + total_time += time; + } + } + + Ok(TransformationResult { + path_coordinates: combined_path, + convergence_metrics: if combined_metrics.is_empty() { + None + } else { + Some(combined_metrics) + }, + execution_time_ms: Some(total_time), + }) + } + + /// Batch processes multiple transformations in parallel + pub async fn batch_transform( + configs: Vec, + ) -> Vec> { + // Process each configuration in parallel + let futures = configs.into_iter().map(|config| Self::transform(config)); + + // Collect all results + join_all(futures).await + } + + /// Advanced batch processing with custom parameters for each transformation + pub async fn advanced_batch_transform( + configs: Vec, + ) -> Vec> { + let futures = configs.into_iter().map(|config| { + let basic_config = config.basic_config.clone(); + + async move { + let obstacles = config.obstacles.unwrap_or_default(); + + if obstacles.is_empty() { + // Use basic transformation if no obstacles + Self::transform(basic_config).await + } else { + // Otherwise, use path optimization with obstacles + Self::optimize_path_with_obstacles(basic_config, obstacles).await + } + } + }); + + join_all(futures).await + } + + /// Calculates the quality of a transformation path + pub fn calculate_path_quality( + path: &[Vec], + obstacles: &[Vec], + obstacle_radius: f64, + ) -> Result { + if path.is_empty() { + return Err(BellandeArchError::InvalidCoordinates( + "Path cannot be empty for quality calculation".to_string(), + )); + } + + // Calculate path length + let mut path_length = 0.0; + for i in 0..path.len() - 1 { + path_length += euclidean_distance(&path[i], &path[i + 1])?; + } + + // Calculate minimum distance to obstacles + let mut min_obstacle_distance = f64::MAX; + if !obstacles.is_empty() { + for point in path { + for obstacle in obstacles { + let distance = euclidean_distance(point, obstacle)?; + if distance < min_obstacle_distance { + min_obstacle_distance = distance; + } + } + } + } else { + min_obstacle_distance = f64::MAX; // No obstacles, so distance is "infinite" + } + + // Calculate path smoothness (using angle between segments) + let mut smoothness_penalty = 0.0; + if path.len() >= 3 { + for i in 0..path.len() - 2 { + let v1 = vector_subtract(&path[i + 1], &path[i])?; + let v2 = vector_subtract(&path[i + 2], &path[i + 1])?; + + let dot_product = vector_dot_product(&v1, &v2)?; + let v1_len = vector_length(&v1)?; + let v2_len = vector_length(&v2)?; + + if v1_len > 0.0 && v2_len > 0.0 { + let cos_angle = dot_product / (v1_len * v2_len); + let angle = cos_angle.acos(); + + // Penalty increases with sharper turns + smoothness_penalty += angle; + } + } + } + + // Obstacle proximity penalty (increases as we get closer to obstacle_radius) + let obstacle_penalty = if min_obstacle_distance < obstacle_radius * 2.0 { + (obstacle_radius * 2.0 - min_obstacle_distance) / obstacle_radius + } else { + 0.0 + }; + + // Combine factors into an overall quality score (higher is better) + // We normalize the path length by dividing by the number of segments + let normalized_path_length = path_length / (path.len() - 1) as f64; + let normalized_smoothness = smoothness_penalty / (path.len() - 2).max(1) as f64; + + // Quality formula: We want shorter paths, smoother paths, and paths that avoid obstacles + let quality = + 10.0 - normalized_path_length - normalized_smoothness * 2.0 - obstacle_penalty * 5.0; + + Ok(quality.max(0.0)) // Ensure quality is non-negative + } +} + +/// Subtracts one vector from another +fn vector_subtract(a: &[f64], b: &[f64]) -> Result, BellandeArchError> { + if a.len() != b.len() { + return Err(BellandeArchError::DimensionMismatch(format!( + "Cannot subtract vectors with different dimensions: {} and {}", + a.len(), + b.len() + ))); + } + + Ok(a.iter().zip(b.iter()).map(|(x, y)| x - y).collect()) +} + +/// Calculates the dot product of two vectors +fn vector_dot_product(a: &[f64], b: &[f64]) -> Result { + if a.len() != b.len() { + return Err(BellandeArchError::DimensionMismatch(format!( + "Cannot calculate dot product of vectors with different dimensions: {} and {}", + a.len(), + b.len() + ))); + } + + Ok(a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()) +} + +/// Calculates the Euclidean length of a vector +fn vector_length(v: &[f64]) -> Result { + Ok(v.iter().map(|x| x * x).sum::().sqrt()) +} + +/// Calculates the Euclidean distance between two points +pub fn euclidean_distance(point1: &[f64], point2: &[f64]) -> Result { + if point1.len() != point2.len() { + return Err(BellandeArchError::DimensionMismatch(format!( + "Points have different dimensions: {} and {}", + point1.len(), + point2.len() + ))); + } + + let sum_squared: f64 = point1 + .iter() + .zip(point2.iter()) + .map(|(a, b)| (a - b).powi(2)) + .sum(); + + Ok(sum_squared.sqrt()) +} + +/// Validates if a point is within the bounds of a given space +pub fn is_point_valid(point: &[f64], min_bounds: &[f64], max_bounds: &[f64]) -> bool { + if point.len() != min_bounds.len() || point.len() != max_bounds.len() { + return false; + } + + point + .iter() + .zip(min_bounds.iter().zip(max_bounds.iter())) + .all(|(p, (min, max))| p >= min && p <= max) +} + +/// Converts between different coordinate representations +pub fn convert_coordinate_system( + coordinates: &[f64], + from_system: &str, + to_system: &str, +) -> Result, BellandeArchError> { + match (from_system, to_system) { + ("cartesian", "cartesian") => Ok(coordinates.to_vec()), + + ("cartesian", "polar") => { + if coordinates.len() != 2 { + return Err(BellandeArchError::DimensionMismatch( + "Cartesian to polar conversion requires exactly 2 dimensions".to_string(), + )); + } + + let x = coordinates[0]; + let y = coordinates[1]; + let r = (x.powi(2) + y.powi(2)).sqrt(); + let theta = y.atan2(x); + + Ok(vec![r, theta]) + } + + ("polar", "cartesian") => { + if coordinates.len() != 2 { + return Err(BellandeArchError::DimensionMismatch( + "Polar to Cartesian conversion requires exactly 2 dimensions".to_string(), + )); + } + + let r = coordinates[0]; + let theta = coordinates[1]; + let x = r * theta.cos(); + let y = r * theta.sin(); + + Ok(vec![x, y]) + } + + ("cartesian", "cylindrical") => { + if coordinates.len() != 3 { + return Err(BellandeArchError::DimensionMismatch( + "Cartesian to cylindrical conversion requires exactly 3 dimensions".to_string(), + )); + } + + let x = coordinates[0]; + let y = coordinates[1]; + let z = coordinates[2]; + + let rho = (x.powi(2) + y.powi(2)).sqrt(); + let phi = y.atan2(x); + + Ok(vec![rho, phi, z]) + } + + ("cylindrical", "cartesian") => { + if coordinates.len() != 3 { + return Err(BellandeArchError::DimensionMismatch( + "Cylindrical to Cartesian conversion requires exactly 3 dimensions".to_string(), + )); + } + + let rho = coordinates[0]; + let phi = coordinates[1]; + let z = coordinates[2]; + + let x = rho * phi.cos(); + let y = rho * phi.sin(); + + Ok(vec![x, y, z]) + } + + ("cartesian", "spherical") => { + if coordinates.len() != 3 { + return Err(BellandeArchError::DimensionMismatch( + "Cartesian to spherical conversion requires exactly 3 dimensions".to_string(), + )); + } + + let x = coordinates[0]; + let y = coordinates[1]; + let z = coordinates[2]; + + let r = (x.powi(2) + y.powi(2) + z.powi(2)).sqrt(); + let theta = if r > 0.0 { (z / r).acos() } else { 0.0 }; + let phi = y.atan2(x); + + Ok(vec![r, theta, phi]) + } + + ("spherical", "cartesian") => { + if coordinates.len() != 3 { + return Err(BellandeArchError::DimensionMismatch( + "Spherical to Cartesian conversion requires exactly 3 dimensions".to_string(), + )); + } + + let r = coordinates[0]; + let theta = coordinates[1]; + let phi = coordinates[2]; + + let x = r * theta.sin() * phi.cos(); + let y = r * theta.sin() * phi.sin(); + let z = r * theta.cos(); + + Ok(vec![x, y, z]) + } + + _ => Err(BellandeArchError::InvalidCoordinates(format!( + "Unsupported coordinate system conversion: {} to {}", + from_system, to_system + ))), + } +} + +/// Interpolates between two points to create a sequence of intermediate points +pub fn interpolate_points( + start: &[f64], + end: &[f64], + num_points: usize, +) -> Result>, BellandeArchError> { + if start.len() != end.len() { + return Err(BellandeArchError::DimensionMismatch(format!( + "Points have different dimensions: {} and {}", + start.len(), + end.len() + ))); + } + + if num_points < 2 { + return Err(BellandeArchError::InvalidCoordinates( + "Number of points must be at least 2 (start and end)".to_string(), + )); + } + + let mut result = Vec::with_capacity(num_points); + + // Add the start point + result.push(start.to_vec()); + + // Add intermediate points + if num_points > 2 { + for i in 1..num_points - 1 { + let t = i as f64 / (num_points - 1) as f64; + let mut point = Vec::with_capacity(start.len()); + + for j in 0..start.len() { + let value = start[j] + t * (end[j] - start[j]); + point.push(value); + } + + result.push(point); + } + } + + // Add the end point + result.push(end.to_vec()); + + Ok(result) +} diff --git a/src/algorithm/mod.rs b/src/algorithm/mod.rs index 445a749..f8bd354 100644 --- a/src/algorithm/mod.rs +++ b/src/algorithm/mod.rs @@ -17,7 +17,6 @@ pub mod bellande_limit; pub mod bellande_node_importance; pub mod bellande_particle; pub mod bellande_probability; -pub mod bellande_search_path; pub mod bellande_segment; pub mod bellande_step; pub mod bellande_tree; diff --git a/src/utilities/utilities.rs b/src/utilities/utilities.rs index 645d241..1c14c40 100644 --- a/src/utilities/utilities.rs +++ b/src/utilities/utilities.rs @@ -12,25 +12,3 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . - -use ed25519_dalek::PublicKey; -use serde::{Deserialize, Serialize}; - -impl Serialize for PublicKey { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_bytes(&self.to_bytes()) - } -} - -impl<'de> Deserialize<'de> for PublicKey { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let bytes: [u8; 32] = Deserialize::deserialize(deserializer)?; - PublicKey::from_bytes(&bytes).map_err(serde::de::Error::custom) - } -}