1#![allow(dead_code)]
15type Result<T, E = torsh_core::error::TorshError> = std::result::Result<T, E>;
18
19use crate::parameter::Parameter;
20use crate::GraphData;
21use std::collections::{HashMap, HashSet, VecDeque};
22use torsh_tensor::{
23 creation::{from_vec, randn, zeros},
24 Tensor,
25};
26
27pub struct GraphEditDistance {
29 pub node_cost: f32,
31 pub edge_cost: f32,
33 pub node_subst_cost: f32,
35 pub edge_subst_cost: f32,
37}
38
39impl GraphEditDistance {
40 pub fn new() -> Self {
42 Self {
43 node_cost: 1.0,
44 edge_cost: 1.0,
45 node_subst_cost: 1.0,
46 edge_subst_cost: 1.0,
47 }
48 }
49
50 pub fn compute(&self, graph1: &GraphData, graph2: &GraphData) -> Result<f32> {
55 let n1 = graph1.num_nodes;
56 let n2 = graph2.num_nodes;
57
58 let node_ops = ((n1 as i32 - n2 as i32).abs() as f32) * self.node_cost;
60
61 let e1 = graph1.num_edges;
63 let e2 = graph2.num_edges;
64 let edge_ops = ((e1 as i32 - e2 as i32).abs() as f32) * self.edge_cost;
65
66 let feature_cost = self.compute_feature_distance(graph1, graph2)?;
68
69 Ok(node_ops + edge_ops + feature_cost)
70 }
71
72 fn compute_feature_distance(&self, graph1: &GraphData, graph2: &GraphData) -> Result<f32> {
74 let f1_data = graph1.x.to_vec()?;
75 let f2_data = graph2.x.to_vec()?;
76
77 let min_len = f1_data.len().min(f2_data.len());
78 let mut dist = 0.0;
79
80 for i in 0..min_len {
81 dist += (f1_data[i] - f2_data[i]).powi(2);
82 }
83
84 dist += ((f1_data.len() as i32 - f2_data.len() as i32).abs() as f32) * self.node_subst_cost;
86
87 Ok(dist.sqrt())
88 }
89
90 pub fn node_correspondence(
92 &self,
93 graph1: &GraphData,
94 graph2: &GraphData,
95 ) -> Result<Vec<(usize, usize)>> {
96 let mut correspondences = Vec::new();
97 let n1 = graph1.num_nodes;
98 let n2 = graph2.num_nodes;
99
100 let mut matched_nodes2 = HashSet::new();
102
103 for i in 0..n1 {
104 let mut best_match = None;
105 let mut best_similarity = f32::NEG_INFINITY;
106
107 for j in 0..n2 {
108 if matched_nodes2.contains(&j) {
109 continue;
110 }
111
112 let similarity = self.node_similarity(graph1, i, graph2, j)?;
113 if similarity > best_similarity {
114 best_similarity = similarity;
115 best_match = Some(j);
116 }
117 }
118
119 if let Some(j) = best_match {
120 correspondences.push((i, j));
121 matched_nodes2.insert(j);
122 }
123 }
124
125 Ok(correspondences)
126 }
127
128 fn node_similarity(
130 &self,
131 graph1: &GraphData,
132 node1: usize,
133 graph2: &GraphData,
134 node2: usize,
135 ) -> Result<f32> {
136 let f1 = graph1.x.slice_tensor(0, node1, node1 + 1)?;
137 let f2 = graph2.x.slice_tensor(0, node2, node2 + 1)?;
138
139 let dot = f1.dot(&f2.t()?)?.item()?;
141 let norm1 = f1.norm()?.item()?;
142 let norm2 = f2.norm()?.item()?;
143
144 if norm1 > 0.0 && norm2 > 0.0 {
145 Ok(dot / (norm1 * norm2))
146 } else {
147 Ok(0.0)
148 }
149 }
150}
151
152impl Default for GraphEditDistance {
153 fn default() -> Self {
154 Self::new()
155 }
156}
157
158pub struct GraphKernel {
160 kernel_type: GraphKernelType,
161}
162
163#[derive(Debug, Clone, Copy)]
164pub enum GraphKernelType {
165 RandomWalk,
167 ShortestPath,
169 WeisfeilerLehman,
171 Graphlet,
173}
174
175impl GraphKernel {
176 pub fn new(kernel_type: GraphKernelType) -> Self {
178 Self { kernel_type }
179 }
180
181 pub fn compute(&self, graph1: &GraphData, graph2: &GraphData) -> Result<f32> {
186 match self.kernel_type {
187 GraphKernelType::RandomWalk => self.random_walk_kernel(graph1, graph2),
188 GraphKernelType::ShortestPath => self.shortest_path_kernel(graph1, graph2),
189 GraphKernelType::WeisfeilerLehman => self.wl_kernel(graph1, graph2),
190 GraphKernelType::Graphlet => self.graphlet_kernel(graph1, graph2),
191 }
192 }
193
194 fn random_walk_kernel(&self, graph1: &GraphData, graph2: &GraphData) -> Result<f32> {
196 let walks1 = self.sample_random_walks(graph1, 10, 5)?;
198 let walks2 = self.sample_random_walks(graph2, 10, 5)?;
199
200 let mut common_count = 0;
201 for w1 in &walks1 {
202 if walks2.contains(w1) {
203 common_count += 1;
204 }
205 }
206
207 Ok(common_count as f32 / (walks1.len() + walks2.len()) as f32)
208 }
209
210 fn sample_random_walks(
212 &self,
213 graph: &GraphData,
214 num_walks: usize,
215 walk_length: usize,
216 ) -> Result<Vec<Vec<usize>>> {
217 let mut rng = scirs2_core::random::thread_rng();
218 let mut walks = Vec::new();
219 let edge_data = graph.edge_index.to_vec()?;
220
221 let mut adj_list: HashMap<usize, Vec<usize>> = HashMap::new();
223 for i in (0..edge_data.len()).step_by(2) {
224 if i + 1 < edge_data.len() {
225 let src = edge_data[i] as usize;
226 let dst = edge_data[i + 1] as usize;
227 adj_list.entry(src).or_insert_with(Vec::new).push(dst);
228 }
229 }
230
231 for _ in 0..num_walks {
233 if graph.num_nodes == 0 {
234 break;
235 }
236
237 let mut walk = Vec::new();
238 let mut current_node = rng.gen_range(0..graph.num_nodes);
239 walk.push(current_node);
240
241 for _ in 0..walk_length {
242 if let Some(neighbors) = adj_list.get(¤t_node) {
243 if neighbors.is_empty() {
244 break;
245 }
246 let idx = rng.gen_range(0..neighbors.len());
247 current_node = neighbors[idx];
248 walk.push(current_node);
249 } else {
250 break;
251 }
252 }
253
254 walks.push(walk);
255 }
256
257 Ok(walks)
258 }
259
260 fn shortest_path_kernel(&self, graph1: &GraphData, graph2: &GraphData) -> Result<f32> {
262 let sp1 = self.compute_shortest_paths_distribution(graph1)?;
264 let sp2 = self.compute_shortest_paths_distribution(graph2)?;
265
266 let mut intersection = 0.0;
268 for i in 0..sp1.len().min(sp2.len()) {
269 intersection += sp1[i].min(sp2[i]);
270 }
271
272 Ok(intersection)
273 }
274
275 fn compute_shortest_paths_distribution(&self, graph: &GraphData) -> Result<Vec<f32>> {
277 let max_path_len = 10;
278 let mut distribution = vec![0.0; max_path_len];
279
280 let edge_data = graph.edge_index.to_vec()?;
282 let mut adj_list: HashMap<usize, Vec<usize>> = HashMap::new();
283
284 for i in (0..edge_data.len()).step_by(2) {
285 if i + 1 < edge_data.len() {
286 let src = edge_data[i] as usize;
287 let dst = edge_data[i + 1] as usize;
288 adj_list.entry(src).or_insert_with(Vec::new).push(dst);
289 }
290 }
291
292 let num_samples = graph.num_nodes.min(5);
294 for start in 0..num_samples {
295 let path_lengths = self.bfs_shortest_paths(&adj_list, start, graph.num_nodes);
296 for length in path_lengths {
297 if length < max_path_len {
298 distribution[length] += 1.0;
299 }
300 }
301 }
302
303 let sum: f32 = distribution.iter().sum();
305 if sum > 0.0 {
306 for val in &mut distribution {
307 *val /= sum;
308 }
309 }
310
311 Ok(distribution)
312 }
313
314 fn bfs_shortest_paths(
316 &self,
317 adj_list: &HashMap<usize, Vec<usize>>,
318 start: usize,
319 num_nodes: usize,
320 ) -> Vec<usize> {
321 let mut distances = vec![usize::MAX; num_nodes];
322 let mut queue = VecDeque::new();
323
324 distances[start] = 0;
325 queue.push_back(start);
326
327 while let Some(node) = queue.pop_front() {
328 if let Some(neighbors) = adj_list.get(&node) {
329 for &neighbor in neighbors {
330 if neighbor < num_nodes && distances[neighbor] == usize::MAX {
331 distances[neighbor] = distances[node] + 1;
332 queue.push_back(neighbor);
333 }
334 }
335 }
336 }
337
338 distances.into_iter().filter(|&d| d != usize::MAX).collect()
339 }
340
341 fn wl_kernel(&self, graph1: &GraphData, graph2: &GraphData) -> Result<f32> {
343 let labels1 = self.wl_iteration(graph1)?;
345 let labels2 = self.wl_iteration(graph2)?;
346
347 let mut hist1: HashMap<usize, f32> = HashMap::new();
349 let mut hist2: HashMap<usize, f32> = HashMap::new();
350
351 for &label in &labels1 {
352 *hist1.entry(label).or_insert(0.0) += 1.0;
353 }
354 for &label in &labels2 {
355 *hist2.entry(label).or_insert(0.0) += 1.0;
356 }
357
358 let all_labels: HashSet<_> = hist1.keys().chain(hist2.keys()).collect();
360 let mut intersection = 0.0;
361
362 for &&label in &all_labels {
363 let count1 = hist1.get(&label).copied().unwrap_or(0.0);
364 let count2 = hist2.get(&label).copied().unwrap_or(0.0);
365 intersection += count1.min(count2);
366 }
367
368 Ok(intersection / (labels1.len() + labels2.len()) as f32)
369 }
370
371 fn wl_iteration(&self, graph: &GraphData) -> Result<Vec<usize>> {
373 let num_nodes = graph.num_nodes;
374 let labels = vec![0; num_nodes]; let edge_data = graph.edge_index.to_vec()?;
378 let mut adj_list: HashMap<usize, Vec<usize>> = HashMap::new();
379
380 for i in (0..edge_data.len()).step_by(2) {
381 if i + 1 < edge_data.len() {
382 let src = edge_data[i] as usize;
383 let dst = edge_data[i + 1] as usize;
384 adj_list.entry(src).or_insert_with(Vec::new).push(dst);
385 }
386 }
387
388 let mut new_labels = vec![0; num_nodes];
390 for node in 0..num_nodes {
391 let mut neighbor_labels = vec![labels[node]];
392 if let Some(neighbors) = adj_list.get(&node) {
393 for &neighbor in neighbors {
394 if neighbor < num_nodes {
395 neighbor_labels.push(labels[neighbor]);
396 }
397 }
398 }
399 neighbor_labels.sort_unstable();
400
401 new_labels[node] = neighbor_labels
403 .iter()
404 .fold(0usize, |acc, &l| acc.wrapping_mul(31).wrapping_add(l));
405 }
406
407 Ok(new_labels)
408 }
409
410 fn graphlet_kernel(&self, graph1: &GraphData, graph2: &GraphData) -> Result<f32> {
412 let graphlets1 = self.count_graphlets(graph1)?;
414 let graphlets2 = self.count_graphlets(graph2)?;
415
416 let mut similarity = 0.0;
418 for (pattern, &count1) in &graphlets1 {
419 if let Some(&count2) = graphlets2.get(pattern) {
420 similarity += count1.min(count2);
421 }
422 }
423
424 Ok(similarity / (graph1.num_nodes + graph2.num_nodes) as f32)
425 }
426
427 fn count_graphlets(&self, graph: &GraphData) -> Result<HashMap<String, f32>> {
429 let mut counts = HashMap::new();
430
431 let edge_data = graph.edge_index.to_vec()?;
433 let mut adj_list: HashMap<usize, Vec<usize>> = HashMap::new();
434
435 for i in (0..edge_data.len()).step_by(2) {
436 if i + 1 < edge_data.len() {
437 let src = edge_data[i] as usize;
438 let dst = edge_data[i + 1] as usize;
439 adj_list.entry(src).or_insert_with(Vec::new).push(dst);
440 }
441 }
442
443 let mut triangles = 0.0;
445 for (_node, neighbors) in &adj_list {
446 for i in 0..neighbors.len() {
447 for j in (i + 1)..neighbors.len() {
448 let n1 = neighbors[i];
449 let n2 = neighbors[j];
450
451 if let Some(n1_neighbors) = adj_list.get(&n1) {
452 if n1_neighbors.contains(&n2) {
453 triangles += 1.0;
454 }
455 }
456 }
457 }
458 }
459 counts.insert("triangle".to_string(), triangles / 3.0); let mut stars = 0.0;
463 for neighbors in adj_list.values() {
464 if neighbors.len() >= 3 {
465 stars += 1.0;
466 }
467 }
468 counts.insert("star".to_string(), stars);
469
470 Ok(counts)
471 }
472}
473
474#[derive(Debug)]
476pub struct GraphMatchingNetwork {
477 node_embedding_dim: usize,
478 hidden_dim: usize,
479
480 node_encoder1: Parameter,
482 node_encoder2: Parameter,
483
484 attention_query: Parameter,
486 attention_key: Parameter,
487 attention_value: Parameter,
488
489 matching_layer1: Parameter,
491 matching_layer2: Parameter,
492 output_layer: Parameter,
493
494 bias: Option<Parameter>,
495}
496
497impl GraphMatchingNetwork {
498 pub fn new(node_embedding_dim: usize, hidden_dim: usize, use_bias: bool) -> Result<Self> {
500 let node_encoder1 = Parameter::new(randn(&[node_embedding_dim, hidden_dim])?);
501 let node_encoder2 = Parameter::new(randn(&[hidden_dim, hidden_dim])?);
502
503 let attention_query = Parameter::new(randn(&[hidden_dim, hidden_dim])?);
504 let attention_key = Parameter::new(randn(&[hidden_dim, hidden_dim])?);
505 let attention_value = Parameter::new(randn(&[hidden_dim, hidden_dim])?);
506
507 let matching_layer1 = Parameter::new(randn(&[hidden_dim * 2, hidden_dim])?);
508 let matching_layer2 = Parameter::new(randn(&[hidden_dim, (hidden_dim / 2)])?);
509 let output_layer = Parameter::new(randn(&[(hidden_dim / 2), 1])?);
510
511 let bias = if use_bias {
512 Some(Parameter::new(zeros(&[1])?))
513 } else {
514 None
515 };
516
517 Ok(Self {
518 node_embedding_dim,
519 hidden_dim,
520 node_encoder1,
521 node_encoder2,
522 attention_query,
523 attention_key,
524 attention_value,
525 matching_layer1,
526 matching_layer2,
527 output_layer,
528 bias,
529 })
530 }
531
532 pub fn compute_similarity(&self, graph1: &GraphData, graph2: &GraphData) -> Result<f32> {
534 let h1 = self.encode_graph(&graph1.x)?;
536 let h2 = self.encode_graph(&graph2.x)?;
537
538 let attended1 = self.cross_attention(&h1, &h2)?;
540 let attended2 = self.cross_attention(&h2, &h1)?;
541
542 let g1 = attended1.mean(Some(&[0]), false)?;
544 let g2 = attended2.mean(Some(&[0]), false)?;
545
546 let g1_data = g1.to_vec()?;
548 let g2_data = g2.to_vec()?;
549 let mut concat_data = g1_data;
550 concat_data.extend(g2_data);
551
552 let concat = from_vec(
553 concat_data,
554 &[1, self.hidden_dim * 2],
555 torsh_core::device::DeviceType::Cpu,
556 )?;
557
558 let mut h = concat.matmul(&self.matching_layer1.clone_data())?;
560 h = self.relu(&h)?;
561
562 h = h.matmul(&self.matching_layer2.clone_data())?;
563 h = self.relu(&h)?;
564
565 let mut score = h.matmul(&self.output_layer.clone_data())?;
566 if let Some(ref bias) = self.bias {
567 score = score.add(&bias.clone_data())?;
568 }
569
570 let score_val = score.item()?;
572 Ok(1.0 / (1.0 + (-score_val).exp()))
573 }
574
575 fn encode_graph(&self, x: &Tensor) -> Result<Tensor> {
577 let mut h = x.matmul(&self.node_encoder1.clone_data())?;
578 h = self.relu(&h)?;
579 h = h.matmul(&self.node_encoder2.clone_data())?;
580 self.relu(&h)
581 }
582
583 fn cross_attention(&self, query_graph: &Tensor, key_value_graph: &Tensor) -> Result<Tensor> {
585 let _q = query_graph.matmul(&self.attention_query.clone_data())?;
586 let _k = key_value_graph.matmul(&self.attention_key.clone_data())?;
587 let v = key_value_graph.matmul(&self.attention_value.clone_data())?;
588
589 Ok(v.mean(Some(&[0]), false)?.unsqueeze(0)?)
592 }
593
594 fn relu(&self, x: &Tensor) -> Result<Tensor> {
595 let data = x.to_vec()?;
596 let activated: Vec<f32> = data.iter().map(|&v| v.max(0.0)).collect();
597 Ok(from_vec(
598 activated,
599 x.shape().dims(),
600 torsh_core::device::DeviceType::Cpu,
601 )?)
602 }
603
604 fn parameters(&self) -> Vec<Tensor> {
605 let mut params = vec![
606 self.node_encoder1.clone_data(),
607 self.node_encoder2.clone_data(),
608 self.attention_query.clone_data(),
609 self.attention_key.clone_data(),
610 self.attention_value.clone_data(),
611 self.matching_layer1.clone_data(),
612 self.matching_layer2.clone_data(),
613 self.output_layer.clone_data(),
614 ];
615
616 if let Some(ref b) = self.bias {
617 params.push(b.clone_data());
618 }
619
620 params
621 }
622}
623
624#[derive(Debug)]
626pub struct SiameseGraphNetwork {
627 embedding_network: Parameter,
628 hidden_dim: usize,
629 output_dim: usize,
630}
631
632impl SiameseGraphNetwork {
633 pub fn new(input_dim: usize, hidden_dim: usize, output_dim: usize) -> Result<Self> {
635 let embedding_network = Parameter::new(randn(&[input_dim, hidden_dim])?);
636
637 Ok(Self {
638 embedding_network,
639 hidden_dim,
640 output_dim,
641 })
642 }
643
644 pub fn embed(&self, graph: &GraphData) -> Result<Tensor> {
646 let mut h = graph.x.matmul(&self.embedding_network.clone_data())?;
647 h = self.relu(&h)?;
648
649 Ok(h.mean(Some(&[0]), false)?)
651 }
652
653 pub fn contrastive_loss(
655 &self,
656 graph1: &GraphData,
657 graph2: &GraphData,
658 is_similar: bool,
659 margin: f32,
660 ) -> Result<f32> {
661 let emb1 = self.embed(graph1)?;
662 let emb2 = self.embed(graph2)?;
663
664 let diff = emb1.sub(&emb2)?;
666 let dist_sq = diff.dot(&diff)?.item()?;
667 let dist = dist_sq.sqrt();
668
669 if is_similar {
670 Ok(dist_sq)
672 } else {
673 Ok((margin - dist).max(0.0).powi(2))
675 }
676 }
677
678 fn relu(&self, x: &Tensor) -> Result<Tensor> {
679 let data = x.to_vec()?;
680 let activated: Vec<f32> = data.iter().map(|&v| v.max(0.0)).collect();
681 Ok(from_vec(
682 activated,
683 x.shape().dims(),
684 torsh_core::device::DeviceType::Cpu,
685 )?)
686 }
687
688 fn parameters(&self) -> Vec<Tensor> {
689 vec![self.embedding_network.clone_data()]
690 }
691}
692
693#[cfg(test)]
694mod tests {
695 use super::*;
696 use torsh_core::device::DeviceType;
697
698 #[test]
699 fn test_graph_edit_distance() {
700 let features1 = randn(&[4, 3]).unwrap();
701 let features2 = randn(&[5, 3]).unwrap();
702 let edges1 = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0];
703 let edges2 = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0];
704
705 let edge_index1 = from_vec(edges1, &[2, 3], DeviceType::Cpu).unwrap();
706 let edge_index2 = from_vec(edges2, &[2, 4], DeviceType::Cpu).unwrap();
707
708 let graph1 = GraphData::new(features1, edge_index1);
709 let graph2 = GraphData::new(features2, edge_index2);
710
711 let ged = GraphEditDistance::new();
712 let distance = ged
713 .compute(&graph1, &graph2)
714 .expect("operation should succeed");
715
716 assert!(distance > 0.0);
717 }
718
719 #[test]
720 fn test_node_correspondence() {
721 let features1 = randn(&[3, 4]).unwrap();
722 let features2 = randn(&[3, 4]).unwrap();
723 let edges = vec![0.0, 1.0, 1.0, 2.0];
724
725 let edge_index = from_vec(edges, &[2, 2], DeviceType::Cpu).unwrap();
726 let graph1 = GraphData::new(features1, edge_index.clone());
727 let graph2 = GraphData::new(features2, edge_index);
728
729 let ged = GraphEditDistance::new();
730 let correspondences = ged
731 .node_correspondence(&graph1, &graph2)
732 .expect("operation should succeed");
733
734 assert_eq!(correspondences.len(), 3);
735 }
736
737 #[test]
738 fn test_random_walk_kernel() {
739 let features1 = randn(&[4, 3]).unwrap();
740 let features2 = randn(&[4, 3]).unwrap();
741 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0];
742
743 let edge_index = from_vec(edges, &[2, 3], DeviceType::Cpu).unwrap();
744 let graph1 = GraphData::new(features1, edge_index.clone());
745 let graph2 = GraphData::new(features2, edge_index);
746
747 let kernel = GraphKernel::new(GraphKernelType::RandomWalk);
748 let similarity = kernel
749 .compute(&graph1, &graph2)
750 .expect("operation should succeed");
751
752 assert!(similarity >= 0.0 && similarity <= 1.0);
753 }
754
755 #[test]
756 fn test_shortest_path_kernel() {
757 let features1 = randn(&[5, 3]).unwrap();
758 let features2 = randn(&[5, 3]).unwrap();
759 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0];
760
761 let edge_index = from_vec(edges, &[2, 4], DeviceType::Cpu).unwrap();
762 let graph1 = GraphData::new(features1, edge_index.clone());
763 let graph2 = GraphData::new(features2, edge_index);
764
765 let kernel = GraphKernel::new(GraphKernelType::ShortestPath);
766 let similarity = kernel
767 .compute(&graph1, &graph2)
768 .expect("operation should succeed");
769
770 assert!(similarity >= 0.0 && similarity <= 1.0);
771 }
772
773 #[test]
774 fn test_weisfeiler_lehman_kernel() {
775 let features1 = randn(&[4, 3]).unwrap();
776 let features2 = randn(&[4, 3]).unwrap();
777 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0];
778
779 let edge_index = from_vec(edges, &[2, 3], DeviceType::Cpu).unwrap();
780 let graph1 = GraphData::new(features1, edge_index.clone());
781 let graph2 = GraphData::new(features2, edge_index);
782
783 let kernel = GraphKernel::new(GraphKernelType::WeisfeilerLehman);
784 let similarity = kernel
785 .compute(&graph1, &graph2)
786 .expect("operation should succeed");
787
788 assert!(similarity >= 0.0);
789 }
790
791 #[test]
792 fn test_graph_matching_network() {
793 let features1 = randn(&[4, 8]).unwrap();
794 let features2 = randn(&[5, 8]).unwrap();
795 let edges1 = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0];
796 let edges2 = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0];
797
798 let edge_index1 = from_vec(edges1, &[2, 3], DeviceType::Cpu).unwrap();
799 let edge_index2 = from_vec(edges2, &[2, 4], DeviceType::Cpu).unwrap();
800
801 let graph1 = GraphData::new(features1, edge_index1);
802 let graph2 = GraphData::new(features2, edge_index2);
803
804 let gmn = GraphMatchingNetwork::new(8, 16, true).expect("operation should succeed");
805 let similarity = gmn
806 .compute_similarity(&graph1, &graph2)
807 .expect("operation should succeed");
808
809 assert!(similarity >= 0.0 && similarity <= 1.0);
810 }
811
812 #[test]
813 fn test_siamese_network() {
814 let features1 = randn(&[3, 6]).unwrap();
815 let features2 = randn(&[3, 6]).unwrap();
816 let edges = vec![0.0, 1.0, 1.0, 2.0];
817
818 let edge_index = from_vec(edges, &[2, 2], DeviceType::Cpu).unwrap();
819 let graph1 = GraphData::new(features1, edge_index.clone());
820 let graph2 = GraphData::new(features2, edge_index);
821
822 let siamese = SiameseGraphNetwork::new(6, 12, 8).expect("operation should succeed");
823
824 let emb1 = siamese.embed(&graph1);
825 let emb2 = siamese.embed(&graph2).expect("operation should succeed");
826
827 assert_eq!(
828 emb1.expect("operation should succeed").shape().dims(),
829 &[12]
830 );
831 assert_eq!(emb2.shape().dims(), &[12]);
832
833 let loss_similar = siamese
835 .contrastive_loss(&graph1, &graph2, true, 1.0)
836 .expect("operation should succeed");
837 assert!(loss_similar >= 0.0);
838
839 let loss_dissimilar = siamese
841 .contrastive_loss(&graph1, &graph2, false, 1.0)
842 .expect("operation should succeed");
843 assert!(loss_dissimilar >= 0.0);
844 }
845
846 #[test]
847 fn test_graphlet_kernel() {
848 let features1 = randn(&[5, 3]).unwrap();
849 let features2 = randn(&[5, 3]).unwrap();
850 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 0.0, 2.0, 3.0, 3.0, 4.0];
851
852 let edge_index = from_vec(edges, &[2, 5], DeviceType::Cpu).unwrap();
853 let graph1 = GraphData::new(features1, edge_index.clone());
854 let graph2 = GraphData::new(features2, edge_index);
855
856 let kernel = GraphKernel::new(GraphKernelType::Graphlet);
857 let similarity = kernel
858 .compute(&graph1, &graph2)
859 .expect("operation should succeed");
860
861 assert!(similarity >= 0.0);
862 }
863}