1#![allow(dead_code)]
4type Result<T, E = torsh_core::error::TorshError> = std::result::Result<T, E>;
7
8use crate::parameter::Parameter;
9use crate::GraphData;
10use torsh_tensor::{
11 creation::{from_vec, randn, zeros},
12 Tensor,
13};
14
15pub mod global {
17 use super::*;
18
19 pub fn global_mean_pool(graph: &GraphData) -> Result<Tensor> {
21 Ok(graph.x.mean(Some(&[0]), false)?)
23 }
24
25 pub fn global_max_pool(graph: &GraphData) -> Result<Tensor> {
27 Ok(graph.x.max(Some(0), false)?)
29 }
30
31 pub fn global_sum_pool(graph: &GraphData) -> Result<Tensor> {
33 Ok(graph.x.sum_dim(&[0], false)?)
35 }
36
37 pub struct GlobalAttentionPool {
39 gate_nn: Parameter,
40 feat_nn: Parameter,
41 }
42
43 impl GlobalAttentionPool {
44 pub fn new(input_dim: usize, hidden_dim: usize) -> Result<Self> {
46 let gate_nn = Parameter::new(randn(&[input_dim, hidden_dim])?);
47 let feat_nn = Parameter::new(randn(&[input_dim, hidden_dim])?);
48
49 Ok(Self { gate_nn, feat_nn })
50 }
51
52 pub fn forward(&self, graph: &GraphData) -> Result<Tensor> {
54 let gate = graph.x.matmul(&self.gate_nn.clone_data())?.sigmoid()?;
56 let feat = graph.x.matmul(&self.feat_nn.clone_data())?;
57
58 let weighted_features = feat.mul(&gate)?;
60
61 Ok(weighted_features.sum_dim(&[0], false)?)
63 }
64
65 pub fn parameters(&self) -> Vec<Tensor> {
67 vec![self.gate_nn.clone_data(), self.feat_nn.clone_data()]
68 }
69 }
70
71 pub struct Set2Set {
73 input_dim: usize,
74 hidden_dim: usize,
75 num_layers: usize,
76 num_iters: usize,
77 lstm_weights: Vec<Parameter>,
78 attention_weights: Parameter,
79 projection_weights: Parameter,
80 }
81
82 impl Set2Set {
83 pub fn new(
85 input_dim: usize,
86 hidden_dim: usize,
87 num_layers: usize,
88 num_iters: usize,
89 ) -> Result<Self> {
90 let mut lstm_weights = Vec::new();
92 for _ in 0..num_layers {
93 lstm_weights.push(Parameter::new(randn(&[
94 hidden_dim * 4,
95 hidden_dim + input_dim,
96 ])?));
97 }
98
99 let attention_weights = Parameter::new(randn(&[hidden_dim, input_dim])?);
100 let projection_weights = Parameter::new(randn(&[input_dim, hidden_dim])?);
101
102 Ok(Self {
103 input_dim,
104 hidden_dim,
105 num_layers,
106 num_iters,
107 lstm_weights,
108 attention_weights,
109 projection_weights,
110 })
111 }
112
113 pub fn forward(&self, graph: &GraphData) -> Result<Tensor> {
115 let _num_nodes = graph.num_nodes;
116 let mut query = zeros(&[1, self.hidden_dim])?;
117
118 for _ in 0..self.num_iters {
120 let scores = query
122 .matmul(&self.attention_weights.clone_data())?
123 .matmul(&graph.x.t()?)?
124 .softmax(-1)?;
125
126 let attended = scores.matmul(&graph.x)?;
128
129 let projected_attended = attended.matmul(&self.projection_weights.clone_data())?;
131
132 query = query.add(&projected_attended)?;
134 }
135
136 Ok(query.squeeze(0)?)
137 }
138
139 pub fn parameters(&self) -> Vec<Tensor> {
141 let mut params: Vec<Tensor> =
142 self.lstm_weights.iter().map(|p| p.clone_data()).collect();
143 params.push(self.attention_weights.clone_data());
144 params.push(self.projection_weights.clone_data());
145 params
146 }
147 }
148}
149
150pub mod hierarchical {
152 use super::*;
153
154 pub struct DiffPool {
156 embed_dim: usize,
157 assign_dim: usize,
158 embed_gnn: Parameter,
159 assign_gnn: Parameter,
160 link_pred_loss_weight: f64,
161 entropy_loss_weight: f64,
162 }
163
164 impl DiffPool {
165 pub fn new(embed_dim: usize, assign_dim: usize) -> Result<Self> {
167 let embed_gnn = Parameter::new(randn(&[embed_dim, embed_dim])?);
168 let assign_gnn = Parameter::new(randn(&[embed_dim, assign_dim])?);
169
170 Ok(Self {
171 embed_dim,
172 assign_dim,
173 embed_gnn,
174 assign_gnn,
175 link_pred_loss_weight: 1.0,
176 entropy_loss_weight: 1.0,
177 })
178 }
179
180 pub fn forward(&self, graph: &GraphData) -> Result<(GraphData, Tensor)> {
182 let num_nodes = graph.num_nodes;
183
184 let node_embeddings = graph.x.matmul(&self.embed_gnn.clone_data())?;
186
187 let assignment_logits = graph.x.matmul(&self.assign_gnn.clone_data())?;
189 let assignment_matrix = assignment_logits.softmax(-1)?;
190
191 let pooled_features = assignment_matrix.t()?.matmul(&node_embeddings)?;
193
194 let adjacency = self.compute_adjacency_matrix(&graph.edge_index, num_nodes)?;
196 let pooled_adj = assignment_matrix
197 .t()?
198 .matmul(&adjacency)?
199 .matmul(&assignment_matrix)?;
200
201 let (new_edge_index, _) = self.adjacency_to_edge_index(&pooled_adj)?;
203
204 let link_pred_loss =
206 self.compute_link_prediction_loss(&adjacency, &assignment_matrix)?;
207 let entropy_loss = self.compute_entropy_loss(&assignment_matrix)?;
208 let total_aux_loss = link_pred_loss
209 .mul_scalar(self.link_pred_loss_weight as f32)?
210 .add(&entropy_loss.mul_scalar(self.entropy_loss_weight as f32)?)?;
211
212 let pooled_graph = GraphData {
213 x: pooled_features,
214 edge_index: new_edge_index,
215 edge_attr: None,
216 batch: None,
217 num_nodes: self.assign_dim,
218 num_edges: 0, };
220
221 Ok((pooled_graph, total_aux_loss))
222 }
223
224 fn compute_adjacency_matrix(
226 &self,
227 edge_index: &Tensor,
228 num_nodes: usize,
229 ) -> Result<Tensor> {
230 let mut adjacency = zeros(&[num_nodes, num_nodes])?;
231 let edge_data = edge_index.to_vec()?;
232 let edge_list: Vec<Vec<i64>> = vec![
233 edge_data[0..edge_data.len() / 2]
234 .iter()
235 .map(|&x| x as i64)
236 .collect(),
237 edge_data[edge_data.len() / 2..]
238 .iter()
239 .map(|&x| x as i64)
240 .collect(),
241 ];
242
243 for j in 0..edge_list[0].len() {
244 let src = edge_list[0][j] as usize;
245 let dst = edge_list[1][j] as usize;
246 if src < num_nodes && dst < num_nodes {
247 let mut adj_data = adjacency.to_vec()?;
249 adj_data[src * num_nodes + dst] = 1.0;
250 adjacency = torsh_tensor::creation::from_vec(
251 adj_data,
252 &[num_nodes, num_nodes],
253 torsh_core::device::DeviceType::Cpu,
254 )?;
255 }
256 }
257
258 Ok(adjacency)
259 }
260
261 fn adjacency_to_edge_index(&self, adjacency: &Tensor) -> Result<(Tensor, usize)> {
263 let adj_data = adjacency.to_vec()?;
264 let mut edges = Vec::new();
265
266 let shape = adjacency.shape();
268 let (rows, cols) = (shape.dims()[0], shape.dims()[1]);
269 for i in 0..rows {
270 for j in 0..cols {
271 let idx = i * cols + j;
272 if idx < adj_data.len() && adj_data[idx] > 0.5 {
273 edges.push([i as f32, j as f32]);
275 }
276 }
277 }
278
279 if edges.is_empty() {
280 Ok((zeros(&[2, 0])?, 0))
281 } else {
282 let num_edges = edges.len();
283 let mut edge_vec = Vec::with_capacity(2 * num_edges);
284
285 for edge in &edges {
286 edge_vec.push(edge[0]);
287 }
288 for edge in &edges {
289 edge_vec.push(edge[1]);
290 }
291
292 Ok((
293 from_vec(
294 edge_vec.iter().map(|&x| x as f32).collect(),
295 &[2, num_edges],
296 torsh_core::device::DeviceType::Cpu,
297 )?,
298 num_edges,
299 ))
300 }
301 }
302
303 fn compute_link_prediction_loss(
305 &self,
306 adjacency: &Tensor,
307 assignment: &Tensor,
308 ) -> Result<Tensor> {
309 let predicted_adj = assignment.matmul(&assignment.t()?)?;
311
312 let eps = 1e-8;
314 let eps_tensor =
315 torsh_tensor::creation::ones_like(adjacency)?.mul_scalar(eps as f32)?;
316 let one_tensor = torsh_tensor::creation::ones_like(adjacency)?;
317 let pos_loss = adjacency.mul(&predicted_adj.add(&eps_tensor)?.ln()?)?;
318 let neg_loss = one_tensor
319 .sub(adjacency)?
320 .mul(&one_tensor.sub(&predicted_adj)?.add(&eps_tensor)?.ln()?)?;
321
322 Ok(pos_loss.add(&neg_loss)?.mean(None, false)?.neg()?)
323 }
324
325 fn compute_entropy_loss(&self, assignment: &Tensor) -> Result<Tensor> {
327 let eps = 1e-8;
328 let eps_tensor =
329 torsh_tensor::creation::ones_like(assignment)?.mul_scalar(eps as f32)?;
330 let entropy = assignment
331 .mul(&assignment.add(&eps_tensor)?.ln()?)?
332 .sum()?
333 .mean(None, false)?
334 .neg()?;
335 Ok(entropy)
336 }
337
338 pub fn parameters(&self) -> Vec<Tensor> {
340 vec![self.embed_gnn.clone_data(), self.assign_gnn.clone_data()]
341 }
342 }
343
344 pub struct TopKPool {
346 ratio: f32,
347 min_score: Option<f32>,
348 score_layer: Parameter,
349 }
350
351 impl TopKPool {
352 pub fn new(input_dim: usize, ratio: f32, min_score: Option<f32>) -> Result<Self> {
354 let score_layer = Parameter::new(randn(&[input_dim, 1])?);
355
356 Ok(Self {
357 ratio,
358 min_score,
359 score_layer,
360 })
361 }
362
363 pub fn forward(&self, graph: &GraphData) -> Result<GraphData> {
365 let num_nodes = graph.num_nodes;
366 let k = (num_nodes as f32 * self.ratio).ceil() as usize;
367
368 let scores = graph
370 .x
371 .matmul(&self.score_layer.clone_data())?
372 .squeeze(-1)?;
373
374 let (top_scores, top_indices) = self.topk(&scores, k)?;
376
377 let (selected_indices, _selected_scores) = if let Some(min_score) = self.min_score {
379 let valid_mask = top_scores.gt_scalar(min_score)?;
380 let mask_data = valid_mask.to_vec()?;
382 let mask_f32 = mask_data
383 .iter()
384 .map(|&x| if x { 1.0 } else { 0.0 })
385 .collect();
386 let mask_tensor = from_vec(
387 mask_f32,
388 valid_mask.shape().dims(),
389 torsh_core::device::DeviceType::Cpu,
390 )?;
391 let valid_indices = self.masked_select(&top_indices, &mask_tensor)?;
392 let valid_scores = self.masked_select(&top_scores, &mask_tensor)?;
393 (valid_indices, valid_scores)
394 } else {
395 (top_indices, top_scores)
396 };
397
398 let selected_features = self.index_select(&graph.x, &selected_indices, 0)?;
400
401 let (new_edge_index, new_num_edges) =
403 self.filter_edges(&graph.edge_index, &selected_indices)?;
404
405 Ok(GraphData {
406 x: selected_features,
407 edge_index: new_edge_index,
408 edge_attr: graph.edge_attr.clone(), batch: None, num_nodes: selected_indices.shape().dims()[0],
411 num_edges: new_num_edges,
412 })
413 }
414
415 fn topk(&self, tensor: &Tensor, k: usize) -> Result<(Tensor, Tensor)> {
417 let values = tensor.to_vec()?;
418 let mut indexed_values: Vec<(f32, usize)> = values
419 .into_iter()
420 .enumerate()
421 .map(|(i, v)| (v, i))
422 .collect();
423
424 indexed_values
426 .sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
427
428 indexed_values.truncate(k);
430
431 let top_values: Vec<f32> = indexed_values.iter().map(|(v, _)| *v).collect();
432 let top_indices: Vec<f32> = indexed_values.iter().map(|(_, i)| *i as f32).collect();
433
434 let values_tensor = from_vec(top_values, &[k], torsh_core::device::DeviceType::Cpu)?;
435 let indices_tensor = from_vec(top_indices, &[k], torsh_core::device::DeviceType::Cpu)?;
436
437 Ok((values_tensor, indices_tensor))
438 }
439
440 fn masked_select(&self, tensor: &Tensor, mask: &Tensor) -> Result<Tensor> {
442 let values = tensor.to_vec()?;
443 let mask_values = mask.to_vec()?;
444
445 let selected: Vec<f32> = values
446 .into_iter()
447 .zip(mask_values.into_iter())
448 .filter_map(|(v, m)| if m > 0.5 { Some(v) } else { None })
449 .collect();
450
451 let selected_len = selected.len();
452 Ok(from_vec(
453 selected,
454 &[selected_len],
455 torsh_core::device::DeviceType::Cpu,
456 )?)
457 }
458
459 fn index_select(&self, tensor: &Tensor, indices: &Tensor, dim: i64) -> Result<Tensor> {
461 let idx_values = indices.to_vec()?;
462
463 if dim == 0 {
464 let tensor_data = tensor.to_vec()?;
466 let shape = tensor.shape();
467 let cols = shape.dims()[1];
468 let original_data: Vec<Vec<f32>> = tensor_data
469 .chunks(cols)
470 .map(|chunk| chunk.to_vec())
471 .collect();
472 let mut selected_rows = Vec::new();
473
474 for &idx in &idx_values {
475 let idx_usize = idx as usize;
476 if idx_usize < original_data.len() {
477 selected_rows.extend_from_slice(&original_data[idx_usize]);
478 }
479 }
480
481 let num_rows = idx_values.len();
482 let num_cols = if num_rows > 0 {
483 selected_rows.len() / num_rows
484 } else {
485 0
486 };
487
488 Ok(from_vec(
489 selected_rows,
490 &[num_rows, num_cols],
491 torsh_core::device::DeviceType::Cpu,
492 )?)
493 } else {
494 Ok(tensor.clone())
496 }
497 }
498
499 fn filter_edges(
501 &self,
502 edge_index: &Tensor,
503 selected_nodes: &Tensor,
504 ) -> Result<(Tensor, usize)> {
505 let edge_data = edge_index.to_vec()?;
506 let edges = vec![
507 edge_data[0..edge_data.len() / 2]
508 .iter()
509 .map(|&x| x as i64)
510 .collect::<Vec<i64>>(),
511 edge_data[edge_data.len() / 2..]
512 .iter()
513 .map(|&x| x as i64)
514 .collect::<Vec<i64>>(),
515 ];
516 let selected_indices = selected_nodes.to_vec()?;
517
518 let mut node_mapping = std::collections::HashMap::new();
520 for (new_idx, &old_idx) in selected_indices.iter().enumerate() {
521 node_mapping.insert(old_idx as i64, new_idx as i64);
522 }
523
524 let mut filtered_edges = Vec::new();
526 for j in 0..edges[0].len() {
527 let src = edges[0][j];
528 let dst = edges[1][j];
529
530 if let (Some(&new_src), Some(&new_dst)) =
531 (node_mapping.get(&src), node_mapping.get(&dst))
532 {
533 filtered_edges.push([new_src, new_dst]);
534 }
535 }
536
537 if filtered_edges.is_empty() {
538 Ok((zeros(&[2, 0])?, 0))
539 } else {
540 let num_edges = filtered_edges.len();
541 let mut edge_vec = Vec::with_capacity(2 * num_edges);
542
543 for edge in &filtered_edges {
544 edge_vec.push(edge[0]);
545 }
546 for edge in &filtered_edges {
547 edge_vec.push(edge[1]);
548 }
549
550 Ok((
551 from_vec(
552 edge_vec.iter().map(|&x| x as f32).collect(),
553 &[2, num_edges],
554 torsh_core::device::DeviceType::Cpu,
555 )?,
556 num_edges,
557 ))
558 }
559 }
560
561 pub fn parameters(&self) -> Vec<Tensor> {
563 vec![self.score_layer.clone_data()]
564 }
565 }
566
567 pub struct MinCutPool {
569 input_dim: usize,
570 output_dim: usize,
571 assignment_layer: Parameter,
572 }
573
574 impl MinCutPool {
575 pub fn new(input_dim: usize, output_dim: usize) -> Result<Self> {
577 let assignment_layer = Parameter::new(randn(&[input_dim, output_dim])?);
578
579 Ok(Self {
580 input_dim,
581 output_dim,
582 assignment_layer,
583 })
584 }
585
586 pub fn forward(&self, graph: &GraphData) -> Result<(GraphData, Tensor)> {
588 let assignment_logits = graph.x.matmul(&self.assignment_layer.clone_data())?;
590 let assignment_matrix = assignment_logits.softmax(-1)?;
591
592 let pooled_features = assignment_matrix.t()?.matmul(&graph.x)?;
594
595 let adjacency = self.compute_adjacency_matrix(&graph.edge_index, graph.num_nodes)?;
597
598 let pooled_adj = assignment_matrix
600 .t()?
601 .matmul(&adjacency)?
602 .matmul(&assignment_matrix)?;
603
604 let (new_edge_index, new_num_edges) = self.adjacency_to_edge_index(&pooled_adj)?;
606
607 let mincut_loss = self.compute_mincut_loss(&adjacency, &assignment_matrix)?;
609 let orthogonality_loss = self.compute_orthogonality_loss(&assignment_matrix)?;
610 let total_loss = mincut_loss.add(&orthogonality_loss)?;
611
612 let pooled_graph = GraphData {
613 x: pooled_features,
614 edge_index: new_edge_index,
615 edge_attr: None,
616 batch: None,
617 num_nodes: self.output_dim,
618 num_edges: new_num_edges,
619 };
620
621 Ok((pooled_graph, total_loss))
622 }
623
624 fn compute_adjacency_matrix(
626 &self,
627 edge_index: &Tensor,
628 num_nodes: usize,
629 ) -> Result<Tensor> {
630 let mut adjacency = zeros(&[num_nodes, num_nodes])?;
631 let edge_data = edge_index.to_vec()?;
632 let edge_list: Vec<Vec<i64>> = vec![
633 edge_data[0..edge_data.len() / 2]
634 .iter()
635 .map(|&x| x as i64)
636 .collect(),
637 edge_data[edge_data.len() / 2..]
638 .iter()
639 .map(|&x| x as i64)
640 .collect(),
641 ];
642
643 for j in 0..edge_list[0].len() {
644 let src = edge_list[0][j] as usize;
645 let dst = edge_list[1][j] as usize;
646 if src < num_nodes && dst < num_nodes {
647 let mut adj_data = adjacency.to_vec()?;
649 adj_data[src * num_nodes + dst] = 1.0;
650 adjacency = torsh_tensor::creation::from_vec(
651 adj_data,
652 &[num_nodes, num_nodes],
653 torsh_core::device::DeviceType::Cpu,
654 )?;
655 }
656 }
657
658 Ok(adjacency)
659 }
660
661 fn adjacency_to_edge_index(&self, adjacency: &Tensor) -> Result<(Tensor, usize)> {
663 let adj_data = adjacency.to_vec()?;
664 let mut edges = Vec::new();
665
666 let shape = adjacency.shape();
668 let (rows, cols) = (shape.dims()[0], shape.dims()[1]);
669 for i in 0..rows {
670 for j in 0..cols {
671 let idx = i * cols + j;
672 if idx < adj_data.len() && adj_data[idx] > 0.1 {
673 edges.push([i as f32, j as f32]);
675 }
676 }
677 }
678
679 if edges.is_empty() {
680 Ok((zeros(&[2, 0])?, 0))
681 } else {
682 let num_edges = edges.len();
683 let mut edge_vec = Vec::with_capacity(2 * num_edges);
684
685 for edge in &edges {
686 edge_vec.push(edge[0]);
687 }
688 for edge in &edges {
689 edge_vec.push(edge[1]);
690 }
691
692 Ok((
693 from_vec(
694 edge_vec.iter().map(|&x| x as f32).collect(),
695 &[2, num_edges],
696 torsh_core::device::DeviceType::Cpu,
697 )?,
698 num_edges,
699 ))
700 }
701 }
702
703 fn compute_mincut_loss(&self, adjacency: &Tensor, assignment: &Tensor) -> Result<Tensor> {
705 let cut = assignment.t()?.matmul(adjacency)?.matmul(assignment)?;
707 let degree = assignment.sum_dim(&[0], false)?;
709
710 let degree_unsqueezed = degree.unsqueeze(0)?;
712 let degree_t = degree.unsqueeze(1)?;
713 let degree_product = degree_t.matmul(°ree_unsqueezed)?;
714 let eps_tensor =
715 torsh_tensor::creation::ones_like(°ree_product)?.mul_scalar(1e-8_f32)?;
716 let normalized_cut = cut.div(°ree_product.add(&eps_tensor)?)?;
717 let diag_sum = normalized_cut.sum()?;
719 Ok(diag_sum.neg()?)
720 }
721
722 fn compute_orthogonality_loss(&self, assignment: &Tensor) -> Result<Tensor> {
724 let cluster_sizes = assignment.sum()?;
725 let normalized_sizes = cluster_sizes.div(&cluster_sizes.sum()?)?;
726
727 let eps = 1e-8;
729 let eps_tensor =
730 torsh_tensor::creation::ones_like(&normalized_sizes)?.mul_scalar(eps as f32)?;
731 let entropy_loss = normalized_sizes
732 .mul(&normalized_sizes.add(&eps_tensor)?.ln()?)?
733 .sum()?
734 .neg()?;
735 Ok(entropy_loss.neg()?)
736 }
737
738 pub fn parameters(&self) -> Vec<Tensor> {
740 vec![self.assignment_layer.clone_data()]
741 }
742 }
743}