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