1use crate::error::Result;
2use crate::streaming::incremental::IncrementalGraphProcessor;
3use arrow::array::Array;
4use std::collections::{HashMap, HashSet, VecDeque};
5use rand::{Rng, SeedableRng};
6use rand_pcg::Pcg64;
7
8#[derive(Debug, Clone)]
13pub struct SamplingConfig {
14 pub seed: Option<u64>,
15 pub sample_rate: f64, pub min_sample_size: usize, pub max_sample_size: usize, }
19
20impl Default for SamplingConfig {
21 fn default() -> Self {
22 Self {
23 seed: None,
24 sample_rate: 0.1, min_sample_size: 100,
26 max_sample_size: 10000,
27 }
28 }
29}
30
31#[derive(Debug)]
33pub struct NodeSampler {
34 rng: Pcg64,
35 config: SamplingConfig,
36 sampled_nodes: HashSet<String>,
37 #[allow(dead_code)]
38 node_scores: HashMap<String, f64>, }
40
41impl NodeSampler {
42 pub fn new(config: SamplingConfig) -> Self {
43 let rng = if let Some(seed) = config.seed {
44 Pcg64::seed_from_u64(seed)
45 } else {
46 Pcg64::from_entropy()
47 };
48
49 Self {
50 rng,
51 config,
52 sampled_nodes: HashSet::new(),
53 node_scores: HashMap::new(),
54 }
55 }
56
57 pub fn uniform_sample(&mut self, processor: &IncrementalGraphProcessor) -> Result<HashSet<String>> {
59 let graph = processor.graph();
60 let nodes_batch = &graph.nodes;
61
62 let mut all_nodes = Vec::new();
63 if nodes_batch.num_rows() > 0 {
64 let node_ids = nodes_batch.column(0)
65 .as_any()
66 .downcast_ref::<arrow::array::StringArray>()
67 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for node IDs"))?;
68
69 for i in 0..node_ids.len() {
70 all_nodes.push(node_ids.value(i).to_string());
71 }
72 }
73
74 let sample_size = self.calculate_sample_size(all_nodes.len());
75 let mut sampled = HashSet::new();
76
77 while sampled.len() < sample_size && sampled.len() < all_nodes.len() {
78 let index = self.rng.gen_range(0..all_nodes.len());
79 sampled.insert(all_nodes[index].clone());
80 }
81
82 self.sampled_nodes = sampled.clone();
83 Ok(sampled)
84 }
85
86 pub fn degree_based_sample(&mut self, processor: &IncrementalGraphProcessor) -> Result<HashSet<String>> {
88 let _graph = processor.graph();
89 let degree_map = self.calculate_node_degrees(processor)?;
90
91 let total_degree: u32 = degree_map.values().sum();
93 let mut cumulative_probs = Vec::new();
94 let mut nodes = Vec::new();
95 let mut cumulative = 0.0;
96
97 for (node, degree) in °ree_map {
98 nodes.push(node.clone());
99 cumulative += (*degree as f64) / (total_degree as f64);
100 cumulative_probs.push(cumulative);
101 }
102
103 let sample_size = self.calculate_sample_size(nodes.len());
104 let mut sampled = HashSet::new();
105
106 for _ in 0..sample_size {
107 let rand_val = self.rng.gen::<f64>();
108
109 for (i, &prob) in cumulative_probs.iter().enumerate() {
111 if rand_val <= prob {
112 sampled.insert(nodes[i].clone());
113 break;
114 }
115 }
116 }
117
118 self.sampled_nodes = sampled.clone();
119 Ok(sampled)
120 }
121
122 pub fn pagerank_based_sample(&mut self, _processor: &IncrementalGraphProcessor, pagerank_scores: &HashMap<String, f64>) -> Result<HashSet<String>> {
124 let total_score: f64 = pagerank_scores.values().sum();
126 let mut cumulative_probs = Vec::new();
127 let mut nodes = Vec::new();
128 let mut cumulative = 0.0;
129
130 for (node, score) in pagerank_scores {
131 nodes.push(node.clone());
132 cumulative += score / total_score;
133 cumulative_probs.push(cumulative);
134 }
135
136 let sample_size = self.calculate_sample_size(nodes.len());
137 let mut sampled = HashSet::new();
138
139 for _ in 0..sample_size {
140 let rand_val = self.rng.gen::<f64>();
141
142 for (i, &prob) in cumulative_probs.iter().enumerate() {
143 if rand_val <= prob {
144 sampled.insert(nodes[i].clone());
145 break;
146 }
147 }
148 }
149
150 self.sampled_nodes = sampled.clone();
151 Ok(sampled)
152 }
153
154 fn calculate_node_degrees(&self, processor: &IncrementalGraphProcessor) -> Result<HashMap<String, u32>> {
156 let graph = processor.graph();
157 let edges_batch = &graph.edges;
158 let mut degrees = HashMap::new();
159
160 if edges_batch.num_rows() > 0 {
161 let source_ids = edges_batch.column(0)
162 .as_any()
163 .downcast_ref::<arrow::array::StringArray>()
164 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for source IDs"))?;
165 let target_ids = edges_batch.column(1)
166 .as_any()
167 .downcast_ref::<arrow::array::StringArray>()
168 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for target IDs"))?;
169
170 for i in 0..source_ids.len() {
171 let source = source_ids.value(i).to_string();
172 let target = target_ids.value(i).to_string();
173
174 *degrees.entry(source).or_insert(0) += 1;
175 *degrees.entry(target).or_insert(0) += 1;
176 }
177 }
178
179 Ok(degrees)
180 }
181
182 fn calculate_sample_size(&self, total_size: usize) -> usize {
183 let target_size = (total_size as f64 * self.config.sample_rate) as usize;
184 target_size.clamp(self.config.min_sample_size, self.config.max_sample_size.min(total_size))
185 }
186
187 pub fn sampled_nodes(&self) -> &HashSet<String> {
188 &self.sampled_nodes
189 }
190}
191
192#[derive(Debug)]
194pub struct EdgeSampler {
195 rng: Pcg64,
196 config: SamplingConfig,
197 sampled_edges: HashSet<(String, String)>,
198}
199
200impl EdgeSampler {
201 pub fn new(config: SamplingConfig) -> Self {
202 let rng = if let Some(seed) = config.seed {
203 Pcg64::seed_from_u64(seed)
204 } else {
205 Pcg64::from_entropy()
206 };
207
208 Self {
209 rng,
210 config,
211 sampled_edges: HashSet::new(),
212 }
213 }
214
215 pub fn uniform_sample(&mut self, processor: &IncrementalGraphProcessor) -> Result<HashSet<(String, String)>> {
217 let graph = processor.graph();
218 let edges_batch = &graph.edges;
219
220 let mut all_edges = Vec::new();
221 if edges_batch.num_rows() > 0 {
222 let source_ids = edges_batch.column(0)
223 .as_any()
224 .downcast_ref::<arrow::array::StringArray>()
225 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for source IDs"))?;
226 let target_ids = edges_batch.column(1)
227 .as_any()
228 .downcast_ref::<arrow::array::StringArray>()
229 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for target IDs"))?;
230
231 for i in 0..source_ids.len() {
232 all_edges.push((source_ids.value(i).to_string(), target_ids.value(i).to_string()));
233 }
234 }
235
236 let sample_size = self.calculate_sample_size(all_edges.len());
237 let mut sampled = HashSet::new();
238
239 while sampled.len() < sample_size && sampled.len() < all_edges.len() {
240 let index = self.rng.gen_range(0..all_edges.len());
241 sampled.insert(all_edges[index].clone());
242 }
243
244 self.sampled_edges = sampled.clone();
245 Ok(sampled)
246 }
247
248 pub fn weight_based_sample(&mut self, processor: &IncrementalGraphProcessor) -> Result<HashSet<(String, String)>> {
250 let graph = processor.graph();
251 let edges_batch = &graph.edges;
252
253 let mut edges_with_weights = Vec::new();
254 let mut total_weight = 0.0;
255
256 if edges_batch.num_rows() > 0 {
257 let source_ids = edges_batch.column(0)
258 .as_any()
259 .downcast_ref::<arrow::array::StringArray>()
260 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for source IDs"))?;
261 let target_ids = edges_batch.column(1)
262 .as_any()
263 .downcast_ref::<arrow::array::StringArray>()
264 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for target IDs"))?;
265 let weights = edges_batch.column(2)
266 .as_any()
267 .downcast_ref::<arrow::array::Float64Array>()
268 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected float64 array for weights"))?;
269
270 for i in 0..source_ids.len() {
271 let weight = weights.value(i).abs(); edges_with_weights.push(((source_ids.value(i).to_string(), target_ids.value(i).to_string()), weight));
273 total_weight += weight;
274 }
275 }
276
277 let mut cumulative_probs = Vec::new();
279 let mut cumulative = 0.0;
280
281 for (_, weight) in &edges_with_weights {
282 cumulative += weight / total_weight;
283 cumulative_probs.push(cumulative);
284 }
285
286 let sample_size = self.calculate_sample_size(edges_with_weights.len());
287 let mut sampled = HashSet::new();
288
289 for _ in 0..sample_size {
290 let rand_val = self.rng.gen::<f64>();
291
292 for (i, &prob) in cumulative_probs.iter().enumerate() {
293 if rand_val <= prob {
294 sampled.insert(edges_with_weights[i].0.clone());
295 break;
296 }
297 }
298 }
299
300 self.sampled_edges = sampled.clone();
301 Ok(sampled)
302 }
303
304 fn calculate_sample_size(&self, total_size: usize) -> usize {
305 let target_size = (total_size as f64 * self.config.sample_rate) as usize;
306 target_size.clamp(self.config.min_sample_size, self.config.max_sample_size.min(total_size))
307 }
308
309 pub fn sampled_edges(&self) -> &HashSet<(String, String)> {
310 &self.sampled_edges
311 }
312}
313
314#[derive(Debug)]
316pub struct SubgraphSampler {
317 rng: Pcg64,
318 config: SamplingConfig,
319 sampled_subgraph: SampledSubgraph,
320}
321
322#[derive(Debug, Clone)]
324pub struct SampledSubgraph {
325 pub nodes: HashSet<String>,
326 pub edges: HashSet<(String, String)>,
327 pub sampling_method: String,
328}
329
330impl SubgraphSampler {
331 pub fn new(config: SamplingConfig) -> Self {
332 let rng = if let Some(seed) = config.seed {
333 Pcg64::seed_from_u64(seed)
334 } else {
335 Pcg64::from_entropy()
336 };
337
338 Self {
339 rng,
340 config,
341 sampled_subgraph: SampledSubgraph {
342 nodes: HashSet::new(),
343 edges: HashSet::new(),
344 sampling_method: "none".to_string(),
345 },
346 }
347 }
348
349 pub fn random_walk_sample(&mut self, processor: &IncrementalGraphProcessor, walk_length: usize) -> Result<SampledSubgraph> {
351 let _graph = processor.graph();
352 let adjacency = self.build_adjacency_list(processor)?;
353
354 let all_nodes: Vec<String> = adjacency.keys().cloned().collect();
356 if all_nodes.is_empty() {
357 return Ok(SampledSubgraph {
358 nodes: HashSet::new(),
359 edges: HashSet::new(),
360 sampling_method: "random_walk".to_string(),
361 });
362 }
363
364 let start_node = &all_nodes[self.rng.gen_range(0..all_nodes.len())];
366 let mut visited_nodes = HashSet::new();
367 let mut visited_edges = HashSet::new();
368 let mut current_node = start_node.clone();
369
370 visited_nodes.insert(current_node.clone());
371
372 for _ in 0..walk_length {
373 if let Some(neighbors) = adjacency.get(¤t_node) {
374 if neighbors.is_empty() {
375 break; }
377
378 let next_node = &neighbors[self.rng.gen_range(0..neighbors.len())];
379 visited_edges.insert((current_node.clone(), next_node.clone()));
380 visited_nodes.insert(next_node.clone());
381 current_node = next_node.clone();
382 } else {
383 break; }
385 }
386
387 let subgraph = SampledSubgraph {
388 nodes: visited_nodes,
389 edges: visited_edges,
390 sampling_method: "random_walk".to_string(),
391 };
392
393 self.sampled_subgraph = subgraph.clone();
394 Ok(subgraph)
395 }
396
397 pub fn bfs_sample(&mut self, processor: &IncrementalGraphProcessor, num_seeds: usize, max_depth: usize) -> Result<SampledSubgraph> {
399 let adjacency = self.build_adjacency_list(processor)?;
400 let all_nodes: Vec<String> = adjacency.keys().cloned().collect();
401
402 if all_nodes.is_empty() {
403 return Ok(SampledSubgraph {
404 nodes: HashSet::new(),
405 edges: HashSet::new(),
406 sampling_method: "bfs".to_string(),
407 });
408 }
409
410 let mut visited_nodes = HashSet::new();
411 let mut visited_edges = HashSet::new();
412 let mut queue = VecDeque::new();
413
414 let actual_seeds = num_seeds.min(all_nodes.len());
416 let mut selected_seeds = HashSet::new();
417
418 while selected_seeds.len() < actual_seeds {
419 let seed = &all_nodes[self.rng.gen_range(0..all_nodes.len())];
420 if selected_seeds.insert(seed.clone()) {
421 queue.push_back((seed.clone(), 0)); visited_nodes.insert(seed.clone());
423 }
424 }
425
426 while let Some((current_node, depth)) = queue.pop_front() {
428 if depth >= max_depth {
429 continue;
430 }
431
432 if let Some(neighbors) = adjacency.get(¤t_node) {
433 for neighbor in neighbors {
434 visited_edges.insert((current_node.clone(), neighbor.clone()));
435
436 if visited_nodes.insert(neighbor.clone()) {
437 queue.push_back((neighbor.clone(), depth + 1));
438 }
439 }
440 }
441 }
442
443 let subgraph = SampledSubgraph {
444 nodes: visited_nodes,
445 edges: visited_edges,
446 sampling_method: "bfs".to_string(),
447 };
448
449 self.sampled_subgraph = subgraph.clone();
450 Ok(subgraph)
451 }
452
453 pub fn forest_fire_sample(&mut self, processor: &IncrementalGraphProcessor, burn_probability: f64) -> Result<SampledSubgraph> {
455 let adjacency = self.build_adjacency_list(processor)?;
456 let all_nodes: Vec<String> = adjacency.keys().cloned().collect();
457
458 if all_nodes.is_empty() {
459 return Ok(SampledSubgraph {
460 nodes: HashSet::new(),
461 edges: HashSet::new(),
462 sampling_method: "forest_fire".to_string(),
463 });
464 }
465
466 let start_node = &all_nodes[self.rng.gen_range(0..all_nodes.len())];
468 let mut visited_nodes = HashSet::new();
469 let mut visited_edges = HashSet::new();
470 let mut burning_queue = VecDeque::new();
471
472 visited_nodes.insert(start_node.clone());
473 burning_queue.push_back(start_node.clone());
474
475 let target_size = self.calculate_sample_size(all_nodes.len());
476
477 while !burning_queue.is_empty() && visited_nodes.len() < target_size {
478 let current_node = burning_queue.pop_front().unwrap();
479
480 if let Some(neighbors) = adjacency.get(¤t_node) {
481 for neighbor in neighbors {
482 if self.rng.gen::<f64>() < burn_probability {
484 visited_edges.insert((current_node.clone(), neighbor.clone()));
485
486 if visited_nodes.insert(neighbor.clone()) {
487 burning_queue.push_back(neighbor.clone());
488 }
489 }
490 }
491 }
492 }
493
494 let subgraph = SampledSubgraph {
495 nodes: visited_nodes,
496 edges: visited_edges,
497 sampling_method: "forest_fire".to_string(),
498 };
499
500 self.sampled_subgraph = subgraph.clone();
501 Ok(subgraph)
502 }
503
504 fn build_adjacency_list(&self, processor: &IncrementalGraphProcessor) -> Result<HashMap<String, Vec<String>>> {
506 let graph = processor.graph();
507 let edges_batch = &graph.edges;
508 let mut adjacency = HashMap::new();
509
510 if edges_batch.num_rows() > 0 {
511 let source_ids = edges_batch.column(0)
512 .as_any()
513 .downcast_ref::<arrow::array::StringArray>()
514 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for source IDs"))?;
515 let target_ids = edges_batch.column(1)
516 .as_any()
517 .downcast_ref::<arrow::array::StringArray>()
518 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for target IDs"))?;
519
520 for i in 0..source_ids.len() {
521 let source = source_ids.value(i).to_string();
522 let target = target_ids.value(i).to_string();
523
524 adjacency.entry(source).or_insert_with(Vec::new).push(target);
525 }
526 }
527
528 Ok(adjacency)
529 }
530
531 fn calculate_sample_size(&self, total_size: usize) -> usize {
532 let target_size = (total_size as f64 * self.config.sample_rate) as usize;
533 target_size.clamp(self.config.min_sample_size, self.config.max_sample_size.min(total_size))
534 }
535
536 pub fn sampled_subgraph(&self) -> &SampledSubgraph {
537 &self.sampled_subgraph
538 }
539}
540
541#[derive(Debug)]
543pub struct ReservoirSampler<T> {
544 reservoir: Vec<T>,
545 capacity: usize,
546 count: usize,
547 rng: Pcg64,
548}
549
550impl<T> ReservoirSampler<T> {
551 pub fn new(capacity: usize, seed: Option<u64>) -> Self {
552 let rng = if let Some(seed) = seed {
553 Pcg64::seed_from_u64(seed)
554 } else {
555 Pcg64::from_entropy()
556 };
557
558 Self {
559 reservoir: Vec::with_capacity(capacity),
560 capacity,
561 count: 0,
562 rng,
563 }
564 }
565
566 pub fn add(&mut self, item: T) {
568 self.count += 1;
569
570 if self.reservoir.len() < self.capacity {
571 self.reservoir.push(item);
573 } else {
574 let j = self.rng.gen_range(0..self.count);
576 if j < self.capacity {
577 self.reservoir[j] = item;
578 }
579 }
580 }
581
582 pub fn sample(&self) -> &[T] {
584 &self.reservoir
585 }
586
587 pub fn sample_size(&self) -> usize {
589 self.reservoir.len()
590 }
591
592 pub fn total_count(&self) -> usize {
594 self.count
595 }
596
597 pub fn reset(&mut self) {
599 self.reservoir.clear();
600 self.count = 0;
601 }
602}
603
604#[derive(Debug)]
606pub struct GraphSamplingProcessor {
607 node_sampler: NodeSampler,
608 edge_sampler: EdgeSampler,
609 subgraph_sampler: SubgraphSampler,
610 reservoir_nodes: ReservoirSampler<String>,
611 reservoir_edges: ReservoirSampler<(String, String)>,
612 config: SamplingConfig,
613}
614
615impl GraphSamplingProcessor {
616 pub fn new(config: SamplingConfig) -> Self {
617 let reservoir_capacity = config.max_sample_size;
618
619 Self {
620 node_sampler: NodeSampler::new(config.clone()),
621 edge_sampler: EdgeSampler::new(config.clone()),
622 subgraph_sampler: SubgraphSampler::new(config.clone()),
623 reservoir_nodes: ReservoirSampler::new(reservoir_capacity, config.seed),
624 reservoir_edges: ReservoirSampler::new(reservoir_capacity, config.seed),
625 config,
626 }
627 }
628
629 pub fn sample_graph(&mut self, processor: &IncrementalGraphProcessor) -> Result<GraphSample> {
631 let uniform_nodes = self.node_sampler.uniform_sample(processor)?;
633 let degree_nodes = self.node_sampler.degree_based_sample(processor)?;
634
635 let uniform_edges = self.edge_sampler.uniform_sample(processor)?;
637 let weight_edges = self.edge_sampler.weight_based_sample(processor)?;
638
639 let random_walk_subgraph = self.subgraph_sampler.random_walk_sample(processor, 100)?;
641 let bfs_subgraph = self.subgraph_sampler.bfs_sample(processor, 5, 3)?;
642 let forest_fire_subgraph = self.subgraph_sampler.forest_fire_sample(processor, 0.7)?;
643
644 Ok(GraphSample {
645 uniform_nodes,
646 degree_based_nodes: degree_nodes,
647 uniform_edges,
648 weight_based_edges: weight_edges,
649 random_walk_subgraph,
650 bfs_subgraph,
651 forest_fire_subgraph,
652 reservoir_nodes: self.reservoir_nodes.sample().to_vec(),
653 reservoir_edges: self.reservoir_edges.sample().to_vec(),
654 })
655 }
656
657 pub fn update_reservoirs(&mut self, processor: &IncrementalGraphProcessor) -> Result<()> {
659 let graph = processor.graph();
660
661 let nodes_batch = &graph.nodes;
663 if nodes_batch.num_rows() > 0 {
664 let node_ids = nodes_batch.column(0)
665 .as_any()
666 .downcast_ref::<arrow::array::StringArray>()
667 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for node IDs"))?;
668
669 for i in 0..node_ids.len() {
670 self.reservoir_nodes.add(node_ids.value(i).to_string());
671 }
672 }
673
674 let edges_batch = &graph.edges;
676 if edges_batch.num_rows() > 0 {
677 let source_ids = edges_batch.column(0)
678 .as_any()
679 .downcast_ref::<arrow::array::StringArray>()
680 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for source IDs"))?;
681 let target_ids = edges_batch.column(1)
682 .as_any()
683 .downcast_ref::<arrow::array::StringArray>()
684 .ok_or_else(|| crate::error::GraphError::graph_construction("Expected string array for target IDs"))?;
685
686 for i in 0..source_ids.len() {
687 self.reservoir_edges.add((source_ids.value(i).to_string(), target_ids.value(i).to_string()));
688 }
689 }
690
691 Ok(())
692 }
693
694 pub fn sampling_stats(&self) -> SamplingStats {
696 SamplingStats {
697 reservoir_node_count: self.reservoir_nodes.sample_size(),
698 reservoir_edge_count: self.reservoir_edges.sample_size(),
699 total_nodes_seen: self.reservoir_nodes.total_count(),
700 total_edges_seen: self.reservoir_edges.total_count(),
701 config: self.config.clone(),
702 }
703 }
704}
705
706#[derive(Debug, Clone)]
708pub struct GraphSample {
709 pub uniform_nodes: HashSet<String>,
710 pub degree_based_nodes: HashSet<String>,
711 pub uniform_edges: HashSet<(String, String)>,
712 pub weight_based_edges: HashSet<(String, String)>,
713 pub random_walk_subgraph: SampledSubgraph,
714 pub bfs_subgraph: SampledSubgraph,
715 pub forest_fire_subgraph: SampledSubgraph,
716 pub reservoir_nodes: Vec<String>,
717 pub reservoir_edges: Vec<(String, String)>,
718}
719
720#[derive(Debug, Clone)]
722pub struct SamplingStats {
723 pub reservoir_node_count: usize,
724 pub reservoir_edge_count: usize,
725 pub total_nodes_seen: usize,
726 pub total_edges_seen: usize,
727 pub config: SamplingConfig,
728}
729
730#[cfg(test)]
731mod tests {
732 use super::*;
733 use crate::graph::ArrowGraph;
734 use arrow::array::{StringArray, Float64Array};
735 use arrow::record_batch::RecordBatch;
736 use arrow::datatypes::{Schema, Field, DataType};
737 use std::sync::Arc;
738
739 fn create_test_graph() -> Result<ArrowGraph> {
740 let nodes_schema = Arc::new(Schema::new(vec![
741 Field::new("id", DataType::Utf8, false),
742 ]));
743 let node_ids = StringArray::from(vec!["A", "B", "C", "D", "E"]);
744 let nodes_batch = RecordBatch::try_new(
745 nodes_schema,
746 vec![Arc::new(node_ids)],
747 )?;
748
749 let edges_schema = Arc::new(Schema::new(vec![
750 Field::new("source", DataType::Utf8, false),
751 Field::new("target", DataType::Utf8, false),
752 Field::new("weight", DataType::Float64, false),
753 ]));
754 let sources = StringArray::from(vec!["A", "B", "C", "D", "A", "B"]);
755 let targets = StringArray::from(vec!["B", "C", "D", "E", "C", "D"]);
756 let weights = Float64Array::from(vec![1.0, 2.0, 3.0, 1.0, 4.0, 2.0]);
757 let edges_batch = RecordBatch::try_new(
758 edges_schema,
759 vec![Arc::new(sources), Arc::new(targets), Arc::new(weights)],
760 )?;
761
762 ArrowGraph::new(nodes_batch, edges_batch)
763 }
764
765 #[test]
766 fn test_node_uniform_sampling() {
767 let graph = create_test_graph().unwrap();
768 let processor = IncrementalGraphProcessor::new(graph).unwrap();
769
770 let config = SamplingConfig {
771 seed: Some(42),
772 sample_rate: 0.6, min_sample_size: 1,
774 max_sample_size: 3,
775 };
776
777 let mut sampler = NodeSampler::new(config);
778 let sampled = sampler.uniform_sample(&processor).unwrap();
779
780 assert!(!sampled.is_empty());
781 assert!(sampled.len() <= 3); let valid_nodes = ["A", "B", "C", "D", "E"];
785 for node in &sampled {
786 assert!(valid_nodes.contains(&node.as_str()));
787 }
788 }
789
790 #[test]
791 fn test_edge_uniform_sampling() {
792 let graph = create_test_graph().unwrap();
793 let processor = IncrementalGraphProcessor::new(graph).unwrap();
794
795 let config = SamplingConfig {
796 seed: Some(42),
797 sample_rate: 0.5,
798 min_sample_size: 1,
799 max_sample_size: 3,
800 };
801
802 let mut sampler = EdgeSampler::new(config);
803 let sampled = sampler.uniform_sample(&processor).unwrap();
804
805 assert!(!sampled.is_empty());
806 assert!(sampled.len() <= 3);
807 }
808
809 #[test]
810 fn test_degree_based_sampling() {
811 let graph = create_test_graph().unwrap();
812 let processor = IncrementalGraphProcessor::new(graph).unwrap();
813
814 let config = SamplingConfig {
815 seed: Some(42),
816 sample_rate: 0.6,
817 min_sample_size: 1,
818 max_sample_size: 3,
819 };
820
821 let mut sampler = NodeSampler::new(config);
822 let sampled = sampler.degree_based_sample(&processor).unwrap();
823
824 assert!(!sampled.is_empty());
825 assert!(sampled.len() <= 3);
826 }
827
828 #[test]
829 fn test_random_walk_sampling() {
830 let graph = create_test_graph().unwrap();
831 let processor = IncrementalGraphProcessor::new(graph).unwrap();
832
833 let config = SamplingConfig {
834 seed: Some(42),
835 sample_rate: 0.5,
836 min_sample_size: 1,
837 max_sample_size: 10,
838 };
839
840 let mut sampler = SubgraphSampler::new(config);
841 let subgraph = sampler.random_walk_sample(&processor, 5).unwrap();
842
843 assert!(!subgraph.nodes.is_empty());
844 assert_eq!(subgraph.sampling_method, "random_walk");
845
846 for (source, target) in &subgraph.edges {
848 assert!(subgraph.nodes.contains(source));
849 assert!(subgraph.nodes.contains(target));
850 }
851 }
852
853 #[test]
854 fn test_bfs_sampling() {
855 let graph = create_test_graph().unwrap();
856 let processor = IncrementalGraphProcessor::new(graph).unwrap();
857
858 let config = SamplingConfig {
859 seed: Some(42),
860 sample_rate: 0.8,
861 min_sample_size: 1,
862 max_sample_size: 10,
863 };
864
865 let mut sampler = SubgraphSampler::new(config);
866 let subgraph = sampler.bfs_sample(&processor, 2, 2).unwrap();
867
868 assert!(!subgraph.nodes.is_empty());
869 assert_eq!(subgraph.sampling_method, "bfs");
870 }
871
872 #[test]
873 fn test_forest_fire_sampling() {
874 let graph = create_test_graph().unwrap();
875 let processor = IncrementalGraphProcessor::new(graph).unwrap();
876
877 let config = SamplingConfig {
878 seed: Some(42),
879 sample_rate: 0.6,
880 min_sample_size: 1,
881 max_sample_size: 10,
882 };
883
884 let mut sampler = SubgraphSampler::new(config);
885 let subgraph = sampler.forest_fire_sample(&processor, 0.7).unwrap();
886
887 assert!(!subgraph.nodes.is_empty());
888 assert_eq!(subgraph.sampling_method, "forest_fire");
889 }
890
891 #[test]
892 fn test_reservoir_sampling() {
893 let mut reservoir = ReservoirSampler::new(3, Some(42));
894
895 for i in 0..10 {
897 reservoir.add(format!("item_{}", i));
898 }
899
900 assert_eq!(reservoir.sample_size(), 3); assert_eq!(reservoir.total_count(), 10); for item in reservoir.sample() {
905 assert!(item.starts_with("item_"));
906 }
907 }
908
909 #[test]
910 fn test_weight_based_edge_sampling() {
911 let graph = create_test_graph().unwrap();
912 let processor = IncrementalGraphProcessor::new(graph).unwrap();
913
914 let config = SamplingConfig {
915 seed: Some(42),
916 sample_rate: 0.5,
917 min_sample_size: 1,
918 max_sample_size: 3,
919 };
920
921 let mut sampler = EdgeSampler::new(config);
922 let sampled = sampler.weight_based_sample(&processor).unwrap();
923
924 assert!(!sampled.is_empty());
925 assert!(sampled.len() <= 3);
926 }
927
928 #[test]
929 fn test_comprehensive_graph_sampling() {
930 let graph = create_test_graph().unwrap();
931 let processor = IncrementalGraphProcessor::new(graph).unwrap();
932
933 let config = SamplingConfig {
934 seed: Some(42),
935 sample_rate: 0.6,
936 min_sample_size: 1,
937 max_sample_size: 5,
938 };
939
940 let mut sampling_processor = GraphSamplingProcessor::new(config);
941 let sample = sampling_processor.sample_graph(&processor).unwrap();
942
943 assert!(!sample.uniform_nodes.is_empty());
945 assert!(!sample.degree_based_nodes.is_empty());
946 assert!(!sample.uniform_edges.is_empty());
947 assert!(!sample.weight_based_edges.is_empty());
948 assert!(!sample.random_walk_subgraph.nodes.is_empty());
949 assert!(!sample.bfs_subgraph.nodes.is_empty());
950 assert!(!sample.forest_fire_subgraph.nodes.is_empty());
951 }
952
953 #[test]
954 fn test_reservoir_updates() {
955 let graph = create_test_graph().unwrap();
956 let processor = IncrementalGraphProcessor::new(graph).unwrap();
957
958 let config = SamplingConfig {
959 seed: Some(42),
960 sample_rate: 0.5,
961 min_sample_size: 1,
962 max_sample_size: 3,
963 };
964
965 let mut sampling_processor = GraphSamplingProcessor::new(config);
966 sampling_processor.update_reservoirs(&processor).unwrap();
967
968 let stats = sampling_processor.sampling_stats();
969 assert!(stats.reservoir_node_count > 0);
970 assert!(stats.reservoir_edge_count > 0);
971 assert!(stats.total_nodes_seen > 0);
972 assert!(stats.total_edges_seen > 0);
973 }
974}