1#![allow(dead_code)]
16type Result<T, E = torsh_core::error::TorshError> = std::result::Result<T, E>;
19
20use crate::parameter::Parameter;
21use crate::{GraphData, GraphLayer};
22use torsh_tensor::{
23 creation::{from_vec, randn, zeros},
24 Tensor,
25};
26
27#[derive(Debug, Clone)]
29pub struct HypergraphData {
30 pub x: Tensor,
32 pub incidence_matrix: Tensor,
34 pub hyperedge_weights: Option<Tensor>,
36 pub hyperedge_features: Option<Tensor>,
38 pub node_degrees: Tensor,
40 pub hyperedge_cardinalities: Tensor,
42 pub num_nodes: usize,
44 pub num_hyperedges: usize,
46}
47
48impl HypergraphData {
49 pub fn new(x: Tensor, incidence_matrix: Tensor) -> Result<Self> {
51 let num_nodes = x.shape().dims()[0];
52 let num_hyperedges = incidence_matrix.shape().dims()[1];
53
54 let node_degrees = incidence_matrix.sum_dim(&[1], false)?;
56
57 let hyperedge_cardinalities = incidence_matrix.sum_dim(&[0], false)?;
59
60 Ok(Self {
61 x,
62 incidence_matrix,
63 hyperedge_weights: None,
64 hyperedge_features: None,
65 node_degrees,
66 hyperedge_cardinalities,
67 num_nodes,
68 num_hyperedges,
69 })
70 }
71
72 pub fn with_hyperedge_weights(mut self, weights: Tensor) -> Self {
74 self.hyperedge_weights = Some(weights);
75 self
76 }
77
78 pub fn with_hyperedge_features(mut self, features: Tensor) -> Self {
80 self.hyperedge_features = Some(features);
81 self
82 }
83
84 pub fn to_graph_clique_expansion(&self) -> Result<GraphData> {
86 let incidence_data = self.incidence_matrix.to_vec()?;
87 let mut edges = Vec::new();
88
89 for e in 0..self.num_hyperedges {
91 let mut nodes_in_hyperedge = Vec::new();
92
93 for v in 0..self.num_nodes {
95 let idx = v * self.num_hyperedges + e;
96 if incidence_data[idx] > 0.0 {
97 nodes_in_hyperedge.push(v as f32);
98 }
99 }
100
101 for i in 0..nodes_in_hyperedge.len() {
103 for j in (i + 1)..nodes_in_hyperedge.len() {
104 edges.extend_from_slice(&[nodes_in_hyperedge[i], nodes_in_hyperedge[j]]);
105 edges.extend_from_slice(&[nodes_in_hyperedge[j], nodes_in_hyperedge[i]]);
106 }
107 }
108 }
109
110 let edge_index = if edges.is_empty() {
111 zeros(&[2, 0])?
112 } else {
113 let num_edges = edges.len() / 2;
114 from_vec(edges, &[2, num_edges], torsh_core::device::DeviceType::Cpu)?
115 };
116
117 Ok(GraphData::new(self.x.clone(), edge_index))
118 }
119
120 pub fn to_graph_star_expansion(&self) -> Result<GraphData> {
122 let incidence_data = self.incidence_matrix.to_vec()?;
123 let mut edges = Vec::new();
124
125 let virtual_node_offset = self.num_nodes;
127
128 for e in 0..self.num_hyperedges {
129 let virtual_node = (virtual_node_offset + e) as f32;
130
131 for v in 0..self.num_nodes {
133 let idx = v * self.num_hyperedges + e;
134 if incidence_data[idx] > 0.0 {
135 let node = v as f32;
136 edges.extend_from_slice(&[node, virtual_node]);
137 edges.extend_from_slice(&[virtual_node, node]);
138 }
139 }
140 }
141
142 let edge_index = if edges.is_empty() {
143 zeros(&[2, 0])?
144 } else {
145 let num_edges = edges.len() / 2;
146 from_vec(edges, &[2, num_edges], torsh_core::device::DeviceType::Cpu)?
147 };
148
149 let virtual_features: Tensor = randn(&[self.num_hyperedges, self.x.shape().dims()[1]])?;
151 let node_data = self.x.to_vec()?;
153 let virtual_data = virtual_features.to_vec()?;
154 let mut extended_data = node_data;
155 extended_data.extend(virtual_data);
156
157 let total_nodes = self.num_nodes + self.num_hyperedges;
158 let features_dim = self.x.shape().dims()[1];
159 let extended_x = from_vec(
160 extended_data,
161 &[total_nodes, features_dim],
162 torsh_core::device::DeviceType::Cpu,
163 )?;
164
165 Ok(GraphData::new(extended_x, edge_index))
166 }
167}
168
169#[derive(Debug)]
171pub struct HGCNConv {
172 in_features: usize,
173 out_features: usize,
174 weight: Parameter,
175 bias: Option<Parameter>,
176 use_attention: bool,
177 attention_weight: Option<Parameter>,
178 dropout: f32,
179}
180
181impl HGCNConv {
182 pub fn new(
184 in_features: usize,
185 out_features: usize,
186 bias: bool,
187 use_attention: bool,
188 dropout: f32,
189 ) -> Result<Self> {
190 let weight = Parameter::new(randn(&[in_features, out_features])?);
191 let bias = if bias {
192 Some(Parameter::new(zeros(&[out_features])?))
193 } else {
194 None
195 };
196
197 let attention_weight = if use_attention {
198 Some(Parameter::new(randn(&[out_features])?))
199 } else {
200 None
201 };
202
203 Ok(Self {
204 in_features,
205 out_features,
206 weight,
207 bias,
208 use_attention,
209 attention_weight,
210 dropout,
211 })
212 }
213
214 pub fn forward(&self, hypergraph: &HypergraphData) -> Result<HypergraphData> {
216 let node_features_transformed = hypergraph.x.matmul(&self.weight.clone_data())?;
219
220 let output_features = if let Some(ref bias) = self.bias {
222 node_features_transformed.add(&bias.clone_data())?
223 } else {
224 node_features_transformed
225 };
226
227 Ok(HypergraphData {
229 x: output_features,
230 incidence_matrix: hypergraph.incidence_matrix.clone(),
231 hyperedge_weights: hypergraph.hyperedge_weights.clone(),
232 hyperedge_features: hypergraph.hyperedge_features.clone(),
233 node_degrees: hypergraph.node_degrees.clone(),
234 hyperedge_cardinalities: hypergraph.hyperedge_cardinalities.clone(),
235 num_nodes: hypergraph.num_nodes,
236 num_hyperedges: hypergraph.num_hyperedges,
237 })
238 }
239
240 fn apply_attention(
242 &self,
243 hyperedge_features: &Tensor,
244 _hypergraph: &HypergraphData,
245 ) -> Result<Tensor> {
246 if let Some(ref attention_weight) = self.attention_weight {
247 let attention_scores = hyperedge_features.matmul(&attention_weight.clone_data())?;
249 let attention_probs = attention_scores.softmax(-1)?;
250
251 let attention_expanded = attention_probs.unsqueeze(-1)?;
253 Ok(hyperedge_features.mul(&attention_expanded)?)
254 } else {
255 Ok(hyperedge_features.clone())
256 }
257 }
258
259 fn normalize_by_degrees(
261 &self,
262 features: &Tensor,
263 hypergraph: &HypergraphData,
264 ) -> Result<Tensor> {
265 let degrees = &hypergraph.node_degrees;
266 let epsilon = 1e-8;
267
268 let safe_degrees = degrees.add_scalar(epsilon)?;
270 let inv_degrees = safe_degrees.reciprocal()?;
271
272 let inv_degrees_squeezed = if inv_degrees.shape().dims().len() > 1 {
275 inv_degrees.squeeze_tensor(1)?
276 } else {
277 inv_degrees
278 };
279 let inv_degrees_expanded = inv_degrees_squeezed.unsqueeze(-1)?;
280 Ok(features.mul(&inv_degrees_expanded)?)
281 }
282}
283
284impl GraphLayer for HGCNConv {
285 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
286 let hypergraph = graph_to_hypergraph(graph)?;
288 let output_hypergraph = HGCNConv::forward(self, &hypergraph)?;
289 output_hypergraph.to_graph_clique_expansion()
290 }
291
292 fn parameters(&self) -> Vec<Tensor> {
293 let mut params = vec![self.weight.clone_data()];
294 if let Some(ref bias) = self.bias {
295 params.push(bias.clone_data());
296 }
297 if let Some(ref attention_weight) = self.attention_weight {
298 params.push(attention_weight.clone_data());
299 }
300 params
301 }
302}
303
304#[derive(Debug)]
306pub struct HyperGATConv {
307 in_features: usize,
308 out_features: usize,
309 heads: usize,
310 query_weight: Parameter,
311 key_weight: Parameter,
312 value_weight: Parameter,
313 hyperedge_attention: Parameter,
314 output_weight: Parameter,
315 bias: Option<Parameter>,
316 dropout: f32,
317}
318
319impl HyperGATConv {
320 pub fn new(
322 in_features: usize,
323 out_features: usize,
324 heads: usize,
325 dropout: f32,
326 bias: bool,
327 ) -> Result<Self> {
328 let head_dim = out_features / heads;
329
330 let query_weight = Parameter::new(randn(&[in_features, out_features])?);
331 let key_weight = Parameter::new(randn(&[in_features, out_features])?);
332 let value_weight = Parameter::new(randn(&[in_features, out_features])?);
333 let hyperedge_attention = Parameter::new(randn(&[heads, 2 * head_dim])?);
334 let output_weight = Parameter::new(randn(&[out_features, out_features])?);
335
336 let bias = if bias {
337 Some(Parameter::new(zeros(&[out_features])?))
338 } else {
339 None
340 };
341
342 Ok(Self {
343 in_features,
344 out_features,
345 heads,
346 query_weight,
347 key_weight,
348 value_weight,
349 hyperedge_attention,
350 output_weight,
351 bias,
352 dropout,
353 })
354 }
355
356 pub fn forward(&self, hypergraph: &HypergraphData) -> Result<HypergraphData> {
358 let num_nodes = hypergraph.num_nodes;
359 let head_dim = self.out_features / self.heads;
360
361 let queries = hypergraph.x.matmul(&self.query_weight.clone_data())?;
363 let keys = hypergraph.x.matmul(&self.key_weight.clone_data())?;
364 let values = hypergraph.x.matmul(&self.value_weight.clone_data())?;
365
366 let q = queries.view(&[num_nodes as i32, self.heads as i32, head_dim as i32])?;
368 let k = keys.view(&[num_nodes as i32, self.heads as i32, head_dim as i32])?;
369 let v = values.view(&[num_nodes as i32, self.heads as i32, head_dim as i32])?;
370
371 let attended_features = self.hyperedge_attention_mechanism(&q, &k, &v, hypergraph);
373
374 let concatenated =
376 attended_features?.view(&[num_nodes as i32, self.out_features as i32])?;
377 let mut output = concatenated.matmul(&self.output_weight.clone_data())?;
378
379 if let Some(ref bias) = self.bias {
381 output = output.add(&bias.clone_data())?;
382 }
383
384 Ok(HypergraphData {
386 x: output,
387 incidence_matrix: hypergraph.incidence_matrix.clone(),
388 hyperedge_weights: hypergraph.hyperedge_weights.clone(),
389 hyperedge_features: hypergraph.hyperedge_features.clone(),
390 node_degrees: hypergraph.node_degrees.clone(),
391 hyperedge_cardinalities: hypergraph.hyperedge_cardinalities.clone(),
392 num_nodes: hypergraph.num_nodes,
393 num_hyperedges: hypergraph.num_hyperedges,
394 })
395 }
396
397 fn hyperedge_attention_mechanism(
399 &self,
400 q: &Tensor,
401 k: &Tensor,
402 v: &Tensor,
403 hypergraph: &HypergraphData,
404 ) -> Result<Tensor> {
405 let num_nodes = hypergraph.num_nodes;
406 let head_dim = self.out_features / self.heads;
407
408 let mut output = zeros(&[num_nodes, self.heads, head_dim])?;
410
411 let incidence_data = hypergraph.incidence_matrix.to_vec()?;
412
413 for e in 0..hypergraph.num_hyperedges {
415 let mut nodes_in_hyperedge = Vec::new();
416
417 for v in 0..num_nodes {
419 let idx = v * hypergraph.num_hyperedges + e;
420 if incidence_data[idx] > 0.0 {
421 nodes_in_hyperedge.push(v);
422 }
423 }
424
425 if nodes_in_hyperedge.len() < 2 {
426 continue; }
428
429 for head in 0..self.heads {
431 self.compute_hyperedge_attention(head, &nodes_in_hyperedge, q, k, v, &mut output)?;
432 }
433 }
434
435 Ok(output)
436 }
437
438 fn compute_hyperedge_attention(
440 &self,
441 head: usize,
442 nodes: &[usize],
443 q: &Tensor,
444 k: &Tensor,
445 v: &Tensor,
446 output: &mut Tensor,
447 ) -> Result<()> {
448 let head_dim = self.out_features / self.heads;
449 let scale = 1.0 / (head_dim as f32).sqrt();
450
451 for &node_i in nodes {
454 let mut aggregated = zeros(&[head_dim])?;
455 let mut total_weight = 0.0;
456
457 for &node_j in nodes {
458 if node_i != node_j {
459 let q_i = q
461 .slice_tensor(0, node_i, node_i + 1)?
462 .slice_tensor(1, head, head + 1)?
463 .squeeze_tensor(0)?
464 .squeeze_tensor(0)?;
465
466 let k_j = k
467 .slice_tensor(0, node_j, node_j + 1)?
468 .slice_tensor(1, head, head + 1)?
469 .squeeze_tensor(0)?
470 .squeeze_tensor(0)?;
471
472 let v_j = v
473 .slice_tensor(0, node_j, node_j + 1)?
474 .slice_tensor(1, head, head + 1)?
475 .squeeze_tensor(0)?
476 .squeeze_tensor(0)?;
477
478 let attention_score = q_i.dot(&k_j)?.mul_scalar(scale)?;
480 let weight = attention_score.exp()?.item()?;
481
482 let weighted_value = v_j.mul_scalar(weight)?;
484 aggregated = aggregated.add(&weighted_value)?;
485 total_weight += weight;
486 }
487 }
488
489 if total_weight > 0.0 {
491 aggregated = aggregated.div_scalar(total_weight)?;
492
493 let aggregated_data = aggregated.to_vec()?;
495 for (j, &val) in aggregated_data.iter().enumerate() {
496 output.set_item(&[node_i, head, j], val)?;
497 }
498 }
499 }
500
501 Ok(())
502 }
503}
504
505impl GraphLayer for HyperGATConv {
506 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
507 let hypergraph = graph_to_hypergraph(graph)?;
508 let output_hypergraph = HyperGATConv::forward(self, &hypergraph)?;
509 output_hypergraph.to_graph_clique_expansion()
510 }
511
512 fn parameters(&self) -> Vec<Tensor> {
513 let mut params = vec![
514 self.query_weight.clone_data(),
515 self.key_weight.clone_data(),
516 self.value_weight.clone_data(),
517 self.hyperedge_attention.clone_data(),
518 self.output_weight.clone_data(),
519 ];
520
521 if let Some(ref bias) = self.bias {
522 params.push(bias.clone_data());
523 }
524
525 params
526 }
527}
528
529#[derive(Debug)]
531pub struct HGNNConv {
532 in_features: usize,
533 out_features: usize,
534 weight: Parameter,
535 bias: Option<Parameter>,
536 use_spectral: bool,
537}
538
539impl HGNNConv {
540 pub fn new(
542 in_features: usize,
543 out_features: usize,
544 bias: bool,
545 use_spectral: bool,
546 ) -> Result<Self> {
547 let weight = Parameter::new(randn(&[in_features, out_features])?);
548 let bias = if bias {
549 Some(Parameter::new(zeros(&[out_features])?))
550 } else {
551 None
552 };
553
554 Ok(Self {
555 in_features,
556 out_features,
557 weight,
558 bias,
559 use_spectral,
560 })
561 }
562
563 pub fn forward(&self, hypergraph: &HypergraphData) -> Result<HypergraphData> {
565 let x_transformed = hypergraph.x.matmul(&self.weight.clone_data())?;
567
568 let output_features = if self.use_spectral {
570 self.spectral_convolution(&x_transformed, hypergraph)
571 } else {
572 self.spatial_convolution(&x_transformed, hypergraph)
573 };
574
575 let output_features = output_features?;
577 let final_features = if let Some(ref bias) = self.bias {
578 output_features.add(&bias.clone_data())?
579 } else {
580 output_features
581 };
582
583 Ok(HypergraphData {
584 x: final_features,
585 incidence_matrix: hypergraph.incidence_matrix.clone(),
586 hyperedge_weights: hypergraph.hyperedge_weights.clone(),
587 hyperedge_features: hypergraph.hyperedge_features.clone(),
588 node_degrees: hypergraph.node_degrees.clone(),
589 hyperedge_cardinalities: hypergraph.hyperedge_cardinalities.clone(),
590 num_nodes: hypergraph.num_nodes,
591 num_hyperedges: hypergraph.num_hyperedges,
592 })
593 }
594
595 fn spectral_convolution(
597 &self,
598 features: &Tensor,
599 hypergraph: &HypergraphData,
600 ) -> Result<Tensor> {
601 let laplacian = self.compute_hypergraph_laplacian(hypergraph);
603
604 Ok(laplacian?.matmul(features)?)
606 }
607
608 fn spatial_convolution(
610 &self,
611 features: &Tensor,
612 hypergraph: &HypergraphData,
613 ) -> Result<Tensor> {
614 let incidence_t = hypergraph.incidence_matrix.transpose(0, 1)?;
616 let hyperedge_features = incidence_t.matmul(features)?;
617
618 let aggregated = hypergraph.incidence_matrix.matmul(&hyperedge_features)?;
620
621 self.normalize_by_degrees(&aggregated, hypergraph)
623 }
624
625 fn compute_hypergraph_laplacian(&self, hypergraph: &HypergraphData) -> Result<Tensor> {
627 let h = &hypergraph.incidence_matrix;
628 let num_nodes = hypergraph.num_nodes;
629
630 let node_degrees = h.sum_dim(&[1], false)?;
632 let hyperedge_degrees = h.sum_dim(&[0], false)?;
633
634 let mut d_v = zeros(&[num_nodes, num_nodes])?;
636 let mut d_e = zeros(&[hypergraph.num_hyperedges, hypergraph.num_hyperedges])?;
637
638 let node_deg_data = node_degrees.to_vec()?;
639 let hyperedge_deg_data = hyperedge_degrees.to_vec()?;
640
641 for i in 0..num_nodes {
643 let degree = node_deg_data[i].max(1e-8); d_v.set_item(&[i, i], degree.powf(-0.5))?;
645 }
646
647 for i in 0..hypergraph.num_hyperedges {
648 let degree = hyperedge_deg_data[i].max(1e-8);
649 d_e.set_item(&[i, i], degree.recip())?;
650 }
651
652 let h_t = h.transpose(0, 1)?;
654 let intermediate = d_v.matmul(h)?.matmul(&d_e)?.matmul(&h_t)?.matmul(&d_v)?;
655
656 let identity = eye(num_nodes);
657 Ok(identity?.sub(&intermediate)?)
658 }
659
660 fn normalize_by_degrees(
662 &self,
663 features: &Tensor,
664 hypergraph: &HypergraphData,
665 ) -> Result<Tensor> {
666 let degrees = &hypergraph.node_degrees;
667 let epsilon = 1e-8;
668
669 let safe_degrees = degrees.add_scalar(epsilon)?;
670 let inv_sqrt_degrees = safe_degrees.pow_scalar(-0.5)?;
671
672 let inv_degrees_squeezed = if inv_sqrt_degrees.shape().dims().len() > 1 {
674 inv_sqrt_degrees.squeeze_tensor(1)?
675 } else {
676 inv_sqrt_degrees
677 };
678 let inv_degrees_expanded = inv_degrees_squeezed.unsqueeze(-1)?;
679
680 Ok(features.mul(&inv_degrees_expanded)?)
681 }
682}
683
684impl GraphLayer for HGNNConv {
685 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
686 let hypergraph = graph_to_hypergraph(graph)?;
687 let output_hypergraph = HGNNConv::forward(self, &hypergraph)?;
688 output_hypergraph.to_graph_clique_expansion()
689 }
690
691 fn parameters(&self) -> Vec<Tensor> {
692 let mut params = vec![self.weight.clone_data()];
693 if let Some(ref bias) = self.bias {
694 params.push(bias.clone_data());
695 }
696 params
697 }
698}
699
700pub mod pooling {
702 use super::*;
703
704 pub fn global_hypergraph_pool(
706 hypergraph: &HypergraphData,
707 method: PoolingMethod,
708 ) -> Result<Tensor> {
709 match method {
710 PoolingMethod::Mean => Ok(hypergraph.x.mean(Some(&[0]), false)?),
711 PoolingMethod::Max => Ok(hypergraph.x.max(Some(0), false)?),
712 PoolingMethod::Sum => Ok(hypergraph.x.sum_dim(&[0], false)?),
713 PoolingMethod::Attention => attention_pool(hypergraph),
714 }
715 }
716
717 pub fn hyperedge_pool(hypergraph: &HypergraphData, method: PoolingMethod) -> Result<Tensor> {
719 let incidence_t = hypergraph.incidence_matrix.transpose(0, 1)?;
720
721 match method {
722 PoolingMethod::Mean => {
723 let hyperedge_features = incidence_t.matmul(&hypergraph.x)?;
725 Ok(hyperedge_features.mean(Some(&[0]), false)?)
726 }
727 PoolingMethod::Max => {
728 let hyperedge_features = incidence_t.matmul(&hypergraph.x)?;
729 Ok(hyperedge_features.max(Some(0), false)?)
730 }
731 PoolingMethod::Sum => {
732 let hyperedge_features = incidence_t.matmul(&hypergraph.x)?;
733 Ok(hyperedge_features.sum_dim(&[0], false)?)
734 }
735 PoolingMethod::Attention => {
736 attention_pool(hypergraph)
738 }
739 }
740 }
741
742 pub fn hierarchical_hypergraph_pool(
744 hypergraph: &HypergraphData,
745 num_clusters: usize,
746 ) -> Result<HypergraphData> {
747 let cluster_assignments = cluster_nodes(hypergraph, num_clusters);
749 coarsen_hypergraph(hypergraph, &cluster_assignments)
750 }
751
752 fn attention_pool(hypergraph: &HypergraphData) -> Result<Tensor> {
754 let attention_scores = hypergraph.x.sum_dim(&[1], false)?;
756 let attention_weights = attention_scores.softmax(0)?;
757 let attention_expanded = attention_weights.unsqueeze(-1)?;
758
759 let weighted_features = hypergraph.x.mul(&attention_expanded)?;
760 Ok(weighted_features.sum_dim(&[0], false)?)
761 }
762
763 fn cluster_nodes(hypergraph: &HypergraphData, num_clusters: usize) -> Vec<usize> {
765 let num_nodes = hypergraph.num_nodes;
766 let mut assignments = vec![0; num_nodes];
767
768 for i in 0..num_nodes {
770 assignments[i] = i % num_clusters;
771 }
772
773 assignments
774 }
775
776 fn coarsen_hypergraph(
778 hypergraph: &HypergraphData,
779 cluster_assignments: &[usize],
780 ) -> Result<HypergraphData> {
781 let num_clusters = cluster_assignments.iter().max().copied().unwrap_or(0) + 1;
782 let original_features = hypergraph.x.shape().dims()[1];
783
784 let mut coarse_features_data = vec![0.0; num_clusters * original_features];
786 let mut cluster_counts = vec![0; num_clusters];
787
788 let node_data = hypergraph.x.to_vec()?;
789
790 for (node, &cluster) in cluster_assignments.iter().enumerate() {
791 cluster_counts[cluster] += 1;
792 for feat in 0..original_features {
793 let node_feat_idx = node * original_features + feat;
794 let cluster_feat_idx = cluster * original_features + feat;
795 coarse_features_data[cluster_feat_idx] += node_data[node_feat_idx];
796 }
797 }
798
799 for cluster in 0..num_clusters {
801 if cluster_counts[cluster] > 0 {
802 for feat in 0..original_features {
803 let cluster_feat_idx = cluster * original_features + feat;
804 coarse_features_data[cluster_feat_idx] /= cluster_counts[cluster] as f32;
805 }
806 }
807 }
808
809 let coarse_features = from_vec(
810 coarse_features_data,
811 &[num_clusters, original_features],
812 torsh_core::device::DeviceType::Cpu,
813 )?;
814
815 let coarse_incidence = zeros(&[num_clusters, hypergraph.num_hyperedges])?;
817
818 HypergraphData::new(coarse_features, coarse_incidence)
819 }
820
821 #[derive(Debug, Clone, Copy)]
823 pub enum PoolingMethod {
824 Mean,
825 Max,
826 Sum,
827 Attention,
828 }
829}
830
831pub mod utils {
833 use super::*;
834
835 pub fn edge_list_to_hypergraph(
837 edges: &[(Vec<usize>, f32)],
838 num_nodes: usize,
839 ) -> Result<HypergraphData> {
840 let num_hyperedges = edges.len();
841 let mut incidence_data = vec![0.0; num_nodes * num_hyperedges];
842 let mut weights = Vec::new();
843
844 for (e, (edge_nodes, weight)) in edges.iter().enumerate() {
845 weights.push(*weight);
846 for &node in edge_nodes {
847 if node < num_nodes {
848 incidence_data[node * num_hyperedges + e] = 1.0;
849 }
850 }
851 }
852
853 let features = randn(&[num_nodes, 16])?; let incidence_matrix = from_vec(
855 incidence_data,
856 &[num_nodes, num_hyperedges],
857 torsh_core::device::DeviceType::Cpu,
858 )?;
859
860 let hyperedge_weights = from_vec(
861 weights,
862 &[num_hyperedges],
863 torsh_core::device::DeviceType::Cpu,
864 )?;
865
866 Ok(HypergraphData::new(features, incidence_matrix)?
867 .with_hyperedge_weights(hyperedge_weights))
868 }
869
870 pub fn random_hypergraph(
872 num_nodes: usize,
873 num_hyperedges: usize,
874 edge_prob: f32,
875 features_dim: usize,
876 ) -> Result<HypergraphData> {
877 let mut rng = scirs2_core::random::thread_rng();
878 let mut incidence_data = vec![0.0; num_nodes * num_hyperedges];
879
880 for e in 0..num_hyperedges {
882 for v in 0..num_nodes {
883 if rng.gen_range(0.0..1.0) < edge_prob {
884 incidence_data[v * num_hyperedges + e] = 1.0;
885 }
886 }
887 }
888
889 let features = randn(&[num_nodes, features_dim])?;
890 let incidence_matrix = from_vec(
891 incidence_data,
892 &[num_nodes, num_hyperedges],
893 torsh_core::device::DeviceType::Cpu,
894 )?;
895
896 HypergraphData::new(features, incidence_matrix)
897 }
898
899 pub fn hypergraph_metrics(hypergraph: &HypergraphData) -> Result<HypergraphMetrics> {
901 let node_degrees = hypergraph.node_degrees.to_vec()?;
902 let hyperedge_cardinalities = hypergraph.hyperedge_cardinalities.to_vec()?;
903
904 let avg_node_degree = node_degrees.iter().sum::<f32>() / node_degrees.len() as f32;
905 let avg_hyperedge_size =
906 hyperedge_cardinalities.iter().sum::<f32>() / hyperedge_cardinalities.len() as f32;
907
908 let density = node_degrees.iter().sum::<f32>()
909 / (hypergraph.num_nodes * hypergraph.num_hyperedges) as f32;
910
911 Ok(HypergraphMetrics {
912 avg_node_degree,
913 avg_hyperedge_size,
914 density,
915 num_nodes: hypergraph.num_nodes,
916 num_hyperedges: hypergraph.num_hyperedges,
917 })
918 }
919
920 #[derive(Debug, Clone)]
922 pub struct HypergraphMetrics {
923 pub avg_node_degree: f32,
924 pub avg_hyperedge_size: f32,
925 pub density: f32,
926 pub num_nodes: usize,
927 pub num_hyperedges: usize,
928 }
929}
930
931pub fn graph_to_hypergraph(graph: &GraphData) -> Result<HypergraphData> {
933 let edge_data = crate::utils::tensor_to_vec2::<f32>(&graph.edge_index)?;
934 let num_edges = edge_data[0].len();
935 let num_nodes = graph.num_nodes;
936
937 let mut incidence_data = vec![0.0; num_nodes * num_edges];
939
940 for e in 0..num_edges {
941 let src = edge_data[0][e] as usize;
942 let dst = edge_data[1][e] as usize;
943
944 if src < num_nodes && dst < num_nodes {
945 incidence_data[src * num_edges + e] = 1.0;
946 incidence_data[dst * num_edges + e] = 1.0;
947 }
948 }
949
950 let incidence_matrix = from_vec(
951 incidence_data,
952 &[num_nodes, num_edges],
953 torsh_core::device::DeviceType::Cpu,
954 )?;
955
956 HypergraphData::new(graph.x.clone(), incidence_matrix)
957}
958
959fn eye(n: usize) -> Result<Tensor> {
961 let mut data = vec![0.0; n * n];
962 for i in 0..n {
963 data[i * n + i] = 1.0;
964 }
965 Ok(from_vec(
966 data,
967 &[n, n],
968 torsh_core::device::DeviceType::Cpu,
969 )?)
970}
971
972#[cfg(test)]
973mod tests {
974 use super::*;
975 use torsh_core::device::DeviceType;
976
977 #[test]
978 fn test_hypergraph_creation() {
979 let features = randn(&[4, 3]).unwrap();
980 let incidence_data = vec![
981 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, ];
986 let incidence_matrix = from_vec(incidence_data, &[4, 3], DeviceType::Cpu).unwrap();
987
988 let hypergraph =
989 HypergraphData::new(features, incidence_matrix).expect("operation should succeed");
990
991 assert_eq!(hypergraph.num_nodes, 4);
992 assert_eq!(hypergraph.num_hyperedges, 3);
993 assert_eq!(hypergraph.x.shape().dims(), &[4, 3]);
994 assert_eq!(hypergraph.incidence_matrix.shape().dims(), &[4, 3]);
995 }
996
997 #[test]
998 fn test_hgcn_layer() {
999 let features = randn(&[3, 4]).unwrap();
1000 let incidence_matrix =
1001 from_vec(vec![1.0, 0.0, 1.0, 1.0, 0.0, 1.0], &[3, 2], DeviceType::Cpu).unwrap();
1002 let hypergraph =
1003 HypergraphData::new(features, incidence_matrix).expect("operation should succeed");
1004
1005 let hgcn = HGCNConv::new(4, 8, true, false, 0.1);
1006 let output = hgcn
1007 .expect("operation should succeed")
1008 .forward(&hypergraph)
1009 .expect("operation should succeed");
1010
1011 assert_eq!(output.x.shape().dims(), &[3, 8]);
1012 assert_eq!(output.num_nodes, 3);
1013 assert_eq!(output.num_hyperedges, 2);
1014 }
1015
1016 #[test]
1017 fn test_hypergraph_to_graph_conversion() {
1018 let features = randn(&[3, 4]).unwrap();
1019 let incidence_matrix =
1020 from_vec(vec![1.0, 0.0, 1.0, 1.0, 0.0, 1.0], &[3, 2], DeviceType::Cpu).unwrap();
1021 let hypergraph =
1022 HypergraphData::new(features, incidence_matrix).expect("operation should succeed");
1023
1024 let graph = hypergraph
1025 .to_graph_clique_expansion()
1026 .expect("operation should succeed");
1027 assert_eq!(graph.num_nodes, 3);
1028
1029 let star_graph = hypergraph
1030 .to_graph_star_expansion()
1031 .expect("operation should succeed");
1032 assert_eq!(star_graph.num_nodes, 5); }
1034
1035 #[test]
1036 fn test_hypergraph_pooling() {
1037 let features = randn(&[4, 6]).unwrap();
1038 let incidence_matrix = from_vec(
1039 vec![1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0],
1040 &[4, 2],
1041 DeviceType::Cpu,
1042 )
1043 .unwrap();
1044 let hypergraph =
1045 HypergraphData::new(features, incidence_matrix).expect("operation should succeed");
1046
1047 let pooled_mean =
1048 pooling::global_hypergraph_pool(&hypergraph, pooling::PoolingMethod::Mean);
1049 assert_eq!(
1050 pooled_mean
1051 .expect("operation should succeed")
1052 .shape()
1053 .dims(),
1054 &[6]
1055 );
1056
1057 let pooled_max = pooling::global_hypergraph_pool(&hypergraph, pooling::PoolingMethod::Max);
1058 assert_eq!(
1059 pooled_max.expect("operation should succeed").shape().dims(),
1060 &[6]
1061 );
1062 }
1063
1064 #[test]
1065 fn test_hypergraph_utils() {
1066 let edges = vec![
1067 (vec![0, 1, 2], 1.0),
1068 (vec![1, 3], 0.8),
1069 (vec![0, 2, 3], 1.2),
1070 ];
1071
1072 let hypergraph =
1073 utils::edge_list_to_hypergraph(&edges, 4).expect("operation should succeed");
1074 assert_eq!(hypergraph.num_nodes, 4);
1075 assert_eq!(hypergraph.num_hyperedges, 3);
1076
1077 let metrics = utils::hypergraph_metrics(&hypergraph).expect("operation should succeed");
1078 assert!(metrics.avg_node_degree > 0.0);
1079 assert!(metrics.avg_hyperedge_size > 0.0);
1080 }
1081
1082 #[test]
1083 fn test_random_hypergraph_generation() {
1084 let hypergraph = utils::random_hypergraph(5, 3, 0.6, 8).expect("operation should succeed");
1085 assert_eq!(hypergraph.num_nodes, 5);
1086 assert_eq!(hypergraph.num_hyperedges, 3);
1087 assert_eq!(hypergraph.x.shape().dims(), &[5, 8]);
1088 }
1089
1090 #[test]
1091 fn test_hypergat_layer() {
1092 let features = randn(&[4, 6]).unwrap();
1093 let incidence_matrix = from_vec(
1094 vec![1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0],
1095 &[4, 2],
1096 DeviceType::Cpu,
1097 )
1098 .unwrap();
1099 let hypergraph =
1100 HypergraphData::new(features, incidence_matrix).expect("operation should succeed");
1101
1102 let hypergat = HyperGATConv::new(6, 12, 3, 0.1, true);
1103 let output = hypergat
1104 .expect("operation should succeed")
1105 .forward(&hypergraph)
1106 .expect("operation should succeed");
1107
1108 assert_eq!(output.x.shape().dims(), &[4, 12]);
1109 assert_eq!(output.num_nodes, 4);
1110 }
1111}