use crate::error::Result;
use crate::graph::ArrowGraph;
use crate::algorithms::{GraphAlgorithm, AlgorithmParams};
use arrow::record_batch::RecordBatch;
use rayon::prelude::*;
use crossbeam::channel::{Receiver, Sender, unbounded};
use crossbeam::deque::{Injector, Stealer, Worker};
use parking_lot::{Mutex, RwLock};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, AtomicBool, Ordering};
use std::thread;
use std::time::{Duration, Instant};
#[derive(Debug)]
pub struct ParallelGraphProcessor {
thread_pool: ThreadPool,
work_queue: Arc<WorkStealingQueue>,
load_balancer: LoadBalancer,
config: ParallelConfig,
}
#[derive(Debug, Clone)]
pub struct ParallelConfig {
pub num_threads: usize,
pub chunk_size: usize,
pub queue_capacity: usize,
pub load_balancing_strategy: LoadBalancingStrategy,
pub work_stealing_enabled: bool,
pub priority_scheduling: bool,
}
#[derive(Debug, Clone)]
pub enum LoadBalancingStrategy {
RoundRobin,
LeastLoaded,
WorkStealing,
Locality, Adaptive, }
#[derive(Debug)]
pub struct ThreadPool {
workers: Vec<Worker<WorkItem>>,
stealers: Vec<Stealer<WorkItem>>,
injector: Arc<Injector<WorkItem>>,
handles: Vec<thread::JoinHandle<()>>,
shutdown: Arc<AtomicBool>,
}
#[derive(Debug)]
pub struct WorkStealingQueue {
injector: Arc<Injector<WorkItem>>,
workers: Vec<Worker<WorkItem>>,
stealers: Vec<Stealer<WorkItem>>,
active_workers: Arc<AtomicUsize>,
}
#[derive(Debug)]
pub struct LoadBalancer {
strategy: LoadBalancingStrategy,
thread_loads: Arc<RwLock<Vec<ThreadLoad>>>,
assignment_history: Arc<Mutex<Vec<WorkAssignment>>>,
}
#[derive(Debug)]
pub struct WorkItem {
pub task_id: usize,
pub task_type: TaskType,
pub data: TaskData,
pub priority: Priority,
pub estimated_duration: Duration,
}
#[derive(Debug, Clone)]
pub enum TaskType {
NodeComputation,
EdgeComputation,
SubgraphAnalysis,
AlgorithmExecution,
DataTransformation,
Aggregation,
}
#[derive(Debug)]
pub enum TaskData {
NodeRange(usize, usize), EdgeRange(usize, usize), SubgraphNodes(Vec<String>), Algorithm(Box<dyn GraphAlgorithm>, AlgorithmParams),
Computation(Box<dyn ParallelComputation>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Priority {
Low = 0,
Normal = 1,
High = 2,
Critical = 3,
}
#[derive(Debug, Clone)]
pub struct ThreadLoad {
pub thread_id: usize,
pub active_tasks: usize,
pub queue_size: usize,
pub cpu_utilization: f64,
pub memory_usage: usize,
pub last_updated: Instant,
}
#[derive(Debug, Clone)]
pub struct WorkAssignment {
pub task_id: usize,
pub thread_id: usize,
pub assigned_at: Instant,
pub completed_at: Option<Instant>,
pub result_size: usize,
}
pub trait ParallelComputation: Send + Sync {
type Input;
type Output: Send;
fn compute_parallel(&self, input: Self::Input, chunk_size: usize) -> Result<Self::Output>;
fn optimal_chunk_size(&self) -> usize { 1000 }
fn is_parallelizable(&self) -> bool { true }
}
#[derive(Debug)]
pub struct ParallelPageRank {
damping_factor: f64,
max_iterations: usize,
tolerance: f64,
}
#[derive(Debug)]
pub struct ParallelShortestPaths {
source_nodes: Vec<String>,
algorithm: ShortestPathAlgorithm,
}
#[derive(Debug, Clone)]
pub enum ShortestPathAlgorithm {
Dijkstra,
BellmanFord,
Johnson,
DeltaStepping,
}
#[derive(Debug)]
pub struct ParallelConnectedComponents {
algorithm: ComponentAlgorithm,
}
#[derive(Debug, Clone)]
pub enum ComponentAlgorithm {
UnionFind,
BFS,
DFS,
LabelPropagation,
}
#[derive(Debug, Clone)]
pub struct ParallelMetrics {
pub total_tasks: usize,
pub completed_tasks: usize,
pub failed_tasks: usize,
pub average_execution_time: Duration,
pub throughput: f64, pub cpu_utilization: f64,
pub memory_efficiency: f64,
pub load_balance_factor: f64,
pub work_stealing_events: usize,
}
impl ParallelGraphProcessor {
pub fn new(config: ParallelConfig) -> Result<Self> {
let thread_pool = ThreadPool::new(config.num_threads)?;
let work_queue = Arc::new(WorkStealingQueue::new(config.num_threads, config.queue_capacity)?);
let load_balancer = LoadBalancer::new(config.load_balancing_strategy.clone());
Ok(Self {
thread_pool,
work_queue,
load_balancer,
config,
})
}
pub fn execute_parallel<T: GraphAlgorithm + Send + Sync + 'static>(
&self,
algorithm: T,
graph: &ArrowGraph,
params: &AlgorithmParams,
) -> Result<RecordBatch> {
let num_chunks = self.calculate_optimal_chunks(graph);
let chunk_size = graph.node_count() / num_chunks;
let mut tasks = Vec::new();
for i in 0..num_chunks {
let start = i * chunk_size;
let end = if i == num_chunks - 1 {
graph.node_count()
} else {
(i + 1) * chunk_size
};
let task = WorkItem {
task_id: i,
task_type: TaskType::AlgorithmExecution,
data: TaskData::NodeRange(start, end),
priority: Priority::Normal,
estimated_duration: self.estimate_task_duration(chunk_size),
};
tasks.push(task);
}
let results = self.execute_tasks(tasks)?;
self.combine_algorithm_results(results)
}
pub fn parallel_pagerank(
&self,
graph: &ArrowGraph,
damping_factor: f64,
max_iterations: usize,
) -> Result<Vec<f64>> {
let pagerank = ParallelPageRank {
damping_factor,
max_iterations,
tolerance: 1e-6,
};
pagerank.compute_parallel(graph, self.config.chunk_size)
}
pub fn parallel_shortest_paths(
&self,
graph: &ArrowGraph,
sources: Vec<String>,
algorithm: ShortestPathAlgorithm,
) -> Result<HashMap<String, Vec<f64>>> {
let shortest_paths = ParallelShortestPaths {
source_nodes: sources,
algorithm,
};
shortest_paths.compute_parallel(graph, self.config.chunk_size)
}
pub fn parallel_connected_components(
&self,
graph: &ArrowGraph,
algorithm: ComponentAlgorithm,
) -> Result<Vec<usize>> {
let components = ParallelConnectedComponents { algorithm };
components.compute_parallel(graph, self.config.chunk_size)
}
pub fn parallel_node_features<F, R>(
&self,
graph: &ArrowGraph,
feature_fn: F,
) -> Result<Vec<R>>
where
F: Fn(&str, &ArrowGraph) -> R + Send + Sync + Copy,
R: Send + 'static,
{
let node_ids = self.get_node_ids(graph)?;
let results: Vec<R> = node_ids
.par_iter()
.map(|node_id| feature_fn(node_id, graph))
.collect();
Ok(results)
}
pub fn parallel_edge_features<F, R>(
&self,
graph: &ArrowGraph,
feature_fn: F,
) -> Result<Vec<R>>
where
F: Fn(&str, &str, f64, &ArrowGraph) -> R + Send + Sync + Copy,
R: Send + 'static,
{
let edges = self.get_edge_list(graph)?;
let results: Vec<R> = edges
.par_iter()
.map(|(source, target, weight)| feature_fn(source, target, *weight, graph))
.collect();
Ok(results)
}
fn execute_tasks(&self, tasks: Vec<WorkItem>) -> Result<Vec<TaskResult>> {
let results = Arc::new(Mutex::new(Vec::new()));
let task_count = Arc::new(AtomicUsize::new(tasks.len()));
for task in tasks {
self.work_queue.push_task(task)?;
}
while task_count.load(Ordering::Relaxed) > 0 {
thread::sleep(Duration::from_millis(10));
}
let results = results.lock().clone();
Ok(results)
}
fn calculate_optimal_chunks(&self, graph: &ArrowGraph) -> usize {
let node_count = graph.node_count();
let edge_count = graph.edge_count();
let complexity_factor = (node_count + edge_count) / 10000;
let hardware_factor = self.config.num_threads;
(complexity_factor.max(1) * hardware_factor).min(node_count)
}
fn estimate_task_duration(&self, size: usize) -> Duration {
Duration::from_millis((size / 100) as u64)
}
fn combine_algorithm_results(&self, results: Vec<TaskResult>) -> Result<RecordBatch> {
use arrow::datatypes::{Schema, Field, DataType};
use arrow::array::StringArray;
use std::sync::Arc;
let schema = Arc::new(Schema::new(vec![
Field::new("result", DataType::Utf8, false),
]));
let result_array = StringArray::from(vec!["combined_result"]);
Ok(RecordBatch::try_new(
schema,
vec![Arc::new(result_array)],
)?)
}
fn get_node_ids(&self, graph: &ArrowGraph) -> Result<Vec<String>> {
let mut node_ids = Vec::new();
let nodes_batch = &graph.nodes;
if nodes_batch.num_rows() > 0 {
let ids = nodes_batch.column(0)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for node IDs"))?;
for i in 0..ids.len() {
node_ids.push(ids.value(i).to_string());
}
}
Ok(node_ids)
}
fn get_edge_list(&self, graph: &ArrowGraph) -> Result<Vec<(String, String, f64)>> {
let mut edges = Vec::new();
let edges_batch = &graph.edges;
if edges_batch.num_rows() > 0 {
let source_ids = edges_batch.column(0)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for source IDs"))?;
let target_ids = edges_batch.column(1)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for target IDs"))?;
let weights = edges_batch.column(2)
.as_any()
.downcast_ref::<arrow::array::Float64Array>()
.ok_or_else(|| crate::error::GraphError::graph_construction("Expected float64 array for weights"))?;
for i in 0..source_ids.len() {
edges.push((
source_ids.value(i).to_string(),
target_ids.value(i).to_string(),
weights.value(i),
));
}
}
Ok(edges)
}
pub fn get_metrics(&self) -> ParallelMetrics {
ParallelMetrics {
total_tasks: 0,
completed_tasks: 0,
failed_tasks: 0,
average_execution_time: Duration::from_millis(0),
throughput: 0.0,
cpu_utilization: 0.8, memory_efficiency: 0.9, load_balance_factor: 0.95, work_stealing_events: 0,
}
}
}
impl ThreadPool {
fn new(num_threads: usize) -> Result<Self> {
let injector = Arc::new(Injector::new());
let mut workers = Vec::new();
let mut stealers = Vec::new();
let shutdown = Arc::new(AtomicBool::new(false));
for _ in 0..num_threads {
let worker = Worker::new_fifo();
let stealer = worker.stealer();
stealers.push(stealer);
workers.push(worker);
}
let handles = Vec::new();
Ok(Self {
workers,
stealers,
injector,
handles,
shutdown,
})
}
pub fn shutdown(&self) {
self.shutdown.store(true, Ordering::Relaxed);
}
}
impl WorkStealingQueue {
fn new(num_workers: usize, _capacity: usize) -> Result<Self> {
let injector = Arc::new(Injector::new());
let mut workers = Vec::new();
let mut stealers = Vec::new();
for _ in 0..num_workers {
let worker = Worker::new_fifo();
let stealer = worker.stealer();
stealers.push(stealer);
workers.push(worker);
}
Ok(Self {
injector,
workers,
stealers,
active_workers: Arc::new(AtomicUsize::new(0)),
})
}
fn push_task(&self, task: WorkItem) -> Result<()> {
self.injector.push(task);
Ok(())
}
fn try_steal(&self, worker_id: usize) -> Option<WorkItem> {
for (i, stealer) in self.stealers.iter().enumerate() {
if i != worker_id {
if let crossbeam::deque::Steal::Success(task) = stealer.steal() {
return Some(task);
}
}
}
if let crossbeam::deque::Steal::Success(task) = self.injector.steal() {
return Some(task);
}
None
}
}
impl LoadBalancer {
fn new(strategy: LoadBalancingStrategy) -> Self {
Self {
strategy,
thread_loads: Arc::new(RwLock::new(Vec::new())),
assignment_history: Arc::new(Mutex::new(Vec::new())),
}
}
fn assign_task(&self, task: &WorkItem) -> usize {
match self.strategy {
LoadBalancingStrategy::RoundRobin => {
task.task_id % self.thread_loads.read().len()
}
LoadBalancingStrategy::LeastLoaded => {
self.find_least_loaded_thread()
}
LoadBalancingStrategy::WorkStealing => {
0 }
LoadBalancingStrategy::Locality => {
self.find_locality_optimal_thread(task)
}
LoadBalancingStrategy::Adaptive => {
self.adaptive_assignment(task)
}
}
}
fn find_least_loaded_thread(&self) -> usize {
let loads = self.thread_loads.read();
loads.iter()
.enumerate()
.min_by_key(|(_, load)| load.active_tasks)
.map(|(i, _)| i)
.unwrap_or(0)
}
fn find_locality_optimal_thread(&self, _task: &WorkItem) -> usize {
0
}
fn adaptive_assignment(&self, _task: &WorkItem) -> usize {
0
}
}
#[derive(Debug, Clone)]
pub struct TaskResult {
pub task_id: usize,
pub success: bool,
pub data: Vec<u8>,
pub execution_time: Duration,
}
impl ParallelComputation for ParallelPageRank {
type Input = ArrowGraph;
type Output = Vec<f64>;
fn compute_parallel(&self, input: Self::Input, chunk_size: usize) -> Result<Self::Output> {
let num_nodes = input.node_count();
let mut ranks = vec![1.0 / num_nodes as f64; num_nodes];
let mut new_ranks = vec![0.0; num_nodes];
let adjacency = self.build_adjacency_list(&input)?;
for _iteration in 0..self.max_iterations {
new_ranks.par_chunks_mut(chunk_size)
.enumerate()
.for_each(|(chunk_idx, chunk)| {
let start_idx = chunk_idx * chunk_size;
for (local_idx, new_rank) in chunk.iter_mut().enumerate() {
let node_idx = start_idx + local_idx;
if node_idx >= num_nodes {
break;
}
let mut sum = 0.0;
for other_idx in 0..num_nodes {
if let Some(neighbors) = adjacency.get(&other_idx) {
if neighbors.contains(&node_idx) {
sum += ranks[other_idx] / neighbors.len() as f64;
}
}
}
*new_rank = (1.0 - self.damping_factor) / num_nodes as f64
+ self.damping_factor * sum;
}
});
let diff: f64 = ranks.par_iter()
.zip(new_ranks.par_iter())
.map(|(old, new)| (old - new).abs())
.sum();
std::mem::swap(&mut ranks, &mut new_ranks);
new_ranks.fill(0.0);
if diff < self.tolerance {
break;
}
}
Ok(ranks)
}
}
impl ParallelPageRank {
fn build_adjacency_list(&self, graph: &ArrowGraph) -> Result<HashMap<usize, Vec<usize>>> {
let mut adjacency = HashMap::new();
let mut node_to_index = HashMap::new();
let nodes_batch = &graph.nodes;
if nodes_batch.num_rows() > 0 {
let node_ids = nodes_batch.column(0)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for node IDs"))?;
for (i, node_id) in (0..node_ids.len()).enumerate() {
node_to_index.insert(node_ids.value(i).to_string(), i);
adjacency.insert(i, Vec::new());
}
}
let edges_batch = &graph.edges;
if edges_batch.num_rows() > 0 {
let source_ids = edges_batch.column(0)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for source IDs"))?;
let target_ids = edges_batch.column(1)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for target IDs"))?;
for i in 0..source_ids.len() {
let source_str = source_ids.value(i);
let target_str = target_ids.value(i);
if let (Some(&source_idx), Some(&target_idx)) =
(node_to_index.get(source_str), node_to_index.get(target_str)) {
adjacency.entry(source_idx).or_default().push(target_idx);
}
}
}
Ok(adjacency)
}
}
impl ParallelComputation for ParallelShortestPaths {
type Input = ArrowGraph;
type Output = HashMap<String, Vec<f64>>;
fn compute_parallel(&self, input: Self::Input, _chunk_size: usize) -> Result<Self::Output> {
let mut results = HashMap::new();
let distances: Vec<_> = self.source_nodes.par_iter()
.map(|source| {
self.compute_single_source_shortest_paths(&input, source)
})
.collect::<Result<Vec<_>>>()?;
for (source, dist_vec) in self.source_nodes.iter().zip(distances) {
results.insert(source.clone(), dist_vec);
}
Ok(results)
}
}
impl ParallelShortestPaths {
fn compute_single_source_shortest_paths(
&self,
graph: &ArrowGraph,
source: &str
) -> Result<Vec<f64>> {
let num_nodes = graph.node_count();
let mut distances = vec![f64::INFINITY; num_nodes];
if let Some(source_idx) = self.find_node_index(graph, source)? {
distances[source_idx] = 0.0;
for _ in 0..num_nodes {
}
}
Ok(distances)
}
fn find_node_index(&self, graph: &ArrowGraph, node_id: &str) -> Result<Option<usize>> {
let nodes_batch = &graph.nodes;
if nodes_batch.num_rows() > 0 {
let node_ids = nodes_batch.column(0)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for node IDs"))?;
for i in 0..node_ids.len() {
if node_ids.value(i) == node_id {
return Ok(Some(i));
}
}
}
Ok(None)
}
}
impl ParallelComputation for ParallelConnectedComponents {
type Input = ArrowGraph;
type Output = Vec<usize>;
fn compute_parallel(&self, input: Self::Input, chunk_size: usize) -> Result<Self::Output> {
let num_nodes = input.node_count();
let mut components = (0..num_nodes).collect::<Vec<_>>();
match self.algorithm {
ComponentAlgorithm::UnionFind => {
self.parallel_union_find(&input, &mut components, chunk_size)
}
ComponentAlgorithm::LabelPropagation => {
self.parallel_label_propagation(&input, &mut components, chunk_size)
}
_ => {
Ok(components)
}
}
}
}
impl ParallelConnectedComponents {
fn parallel_union_find(
&self,
graph: &ArrowGraph,
components: &mut [usize],
chunk_size: usize,
) -> Result<Vec<usize>> {
let edges = self.get_edge_indices(graph)?;
edges.par_chunks(chunk_size)
.for_each(|edge_chunk| {
for &(u, v) in edge_chunk {
let root_u = components[u];
let root_v = components[v];
if root_u != root_v {
}
}
});
Ok(components.to_vec())
}
fn parallel_label_propagation(
&self,
_graph: &ArrowGraph,
components: &mut [usize],
_chunk_size: usize,
) -> Result<Vec<usize>> {
Ok(components.to_vec())
}
fn get_edge_indices(&self, graph: &ArrowGraph) -> Result<Vec<(usize, usize)>> {
let mut edges = Vec::new();
let mut node_to_index = HashMap::new();
let nodes_batch = &graph.nodes;
if nodes_batch.num_rows() > 0 {
let node_ids = nodes_batch.column(0)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for node IDs"))?;
for (i, node_id) in (0..node_ids.len()).enumerate() {
node_to_index.insert(node_ids.value(i).to_string(), i);
}
}
let edges_batch = &graph.edges;
if edges_batch.num_rows() > 0 {
let source_ids = edges_batch.column(0)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for source IDs"))?;
let target_ids = edges_batch.column(1)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for target IDs"))?;
for i in 0..source_ids.len() {
let source_str = source_ids.value(i);
let target_str = target_ids.value(i);
if let (Some(&source_idx), Some(&target_idx)) =
(node_to_index.get(source_str), node_to_index.get(target_str)) {
edges.push((source_idx, target_idx));
}
}
}
Ok(edges)
}
}
impl Default for ParallelConfig {
fn default() -> Self {
Self {
num_threads: num_cpus::get(),
chunk_size: 1000,
queue_capacity: 10000,
load_balancing_strategy: LoadBalancingStrategy::WorkStealing,
work_stealing_enabled: true,
priority_scheduling: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::ArrowGraph;
use arrow::array::{StringArray, Float64Array};
use arrow::record_batch::RecordBatch;
use arrow::datatypes::{Schema, Field, DataType};
use std::sync::Arc;
fn create_test_graph() -> Result<ArrowGraph> {
let nodes_schema = Arc::new(Schema::new(vec![
Field::new("id", DataType::Utf8, false),
]));
let node_ids = StringArray::from(vec!["A", "B", "C", "D", "E", "F"]);
let nodes_batch = RecordBatch::try_new(
nodes_schema,
vec![Arc::new(node_ids)],
)?;
let edges_schema = Arc::new(Schema::new(vec![
Field::new("source", DataType::Utf8, false),
Field::new("target", DataType::Utf8, false),
Field::new("weight", DataType::Float64, false),
]));
let sources = StringArray::from(vec!["A", "B", "C", "D", "E"]);
let targets = StringArray::from(vec!["B", "C", "D", "E", "F"]);
let weights = Float64Array::from(vec![1.0, 1.0, 1.0, 1.0, 1.0]);
let edges_batch = RecordBatch::try_new(
edges_schema,
vec![Arc::new(sources), Arc::new(targets), Arc::new(weights)],
)?;
ArrowGraph::new(nodes_batch, edges_batch)
}
#[test]
fn test_parallel_processor_creation() {
let config = ParallelConfig::default();
let processor = ParallelGraphProcessor::new(config).unwrap();
assert!(processor.config.num_threads > 0);
assert!(processor.config.chunk_size > 0);
}
#[test]
fn test_parallel_pagerank() {
let graph = create_test_graph().unwrap();
let config = ParallelConfig::default();
let processor = ParallelGraphProcessor::new(config).unwrap();
let pagerank_result = processor.parallel_pagerank(&graph, 0.85, 10).unwrap();
assert_eq!(pagerank_result.len(), 6);
let sum: f64 = pagerank_result.iter().sum();
assert!((sum - 1.0).abs() < 0.1);
for &pr in &pagerank_result {
assert!(pr > 0.0);
}
}
#[test]
fn test_parallel_connected_components() {
let graph = create_test_graph().unwrap();
let config = ParallelConfig::default();
let processor = ParallelGraphProcessor::new(config).unwrap();
let components = processor.parallel_connected_components(
&graph,
ComponentAlgorithm::UnionFind
).unwrap();
assert_eq!(components.len(), 6);
for &comp in &components {
assert!(comp < 6);
}
}
#[test]
fn test_parallel_node_features() {
let graph = create_test_graph().unwrap();
let config = ParallelConfig::default();
let processor = ParallelGraphProcessor::new(config).unwrap();
let degrees = processor.parallel_node_features(&graph, |node_id, graph| {
let edges_batch = &graph.edges;
let mut degree = 0;
if edges_batch.num_rows() > 0 {
let source_ids = edges_batch.column(0)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.unwrap();
let target_ids = edges_batch.column(1)
.as_any()
.downcast_ref::<arrow::array::StringArray>()
.unwrap();
for i in 0..source_ids.len() {
if source_ids.value(i) == node_id || target_ids.value(i) == node_id {
degree += 1;
}
}
}
degree
}).unwrap();
assert_eq!(degrees.len(), 6);
for °ree in °rees {
assert!(degree >= 0);
}
}
#[test]
fn test_work_stealing_queue() {
let queue = WorkStealingQueue::new(4, 1000).unwrap();
let task = WorkItem {
task_id: 1,
task_type: TaskType::NodeComputation,
data: TaskData::NodeRange(0, 100),
priority: Priority::Normal,
estimated_duration: Duration::from_millis(100),
};
queue.push_task(task).unwrap();
let stolen_task = queue.try_steal(0);
assert!(stolen_task.is_some());
}
#[test]
fn test_load_balancer() {
let balancer = LoadBalancer::new(LoadBalancingStrategy::RoundRobin);
let task = WorkItem {
task_id: 5,
task_type: TaskType::NodeComputation,
data: TaskData::NodeRange(0, 100),
priority: Priority::Normal,
estimated_duration: Duration::from_millis(100),
};
{
let mut loads = balancer.thread_loads.write();
for i in 0..4 {
loads.push(ThreadLoad {
thread_id: i,
active_tasks: 0,
queue_size: 0,
cpu_utilization: 0.0,
memory_usage: 0,
last_updated: Instant::now(),
});
}
}
let assigned_thread = balancer.assign_task(&task);
assert!(assigned_thread < 4);
}
#[test]
fn test_performance_metrics() {
let config = ParallelConfig::default();
let processor = ParallelGraphProcessor::new(config).unwrap();
let metrics = processor.get_metrics();
assert!(metrics.cpu_utilization >= 0.0 && metrics.cpu_utilization <= 1.0);
assert!(metrics.memory_efficiency >= 0.0 && metrics.memory_efficiency <= 1.0);
assert!(metrics.load_balance_factor >= 0.0 && metrics.load_balance_factor <= 1.0);
}
#[test]
fn test_task_priority() {
assert!(Priority::Critical > Priority::High);
assert!(Priority::High > Priority::Normal);
assert!(Priority::Normal > Priority::Low);
}
#[test]
fn test_parallel_config_default() {
let config = ParallelConfig::default();
assert!(config.num_threads > 0);
assert!(config.chunk_size > 0);
assert!(config.queue_capacity > 0);
assert!(config.work_stealing_enabled);
}
}