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)]
30pub struct GraphFNO {
31 in_features: usize,
32 out_features: usize,
33 hidden_features: usize,
34 num_modes: usize,
35 num_layers: usize,
36
37 fourier_weights: Vec<Parameter>,
39 conv_weights: Vec<Parameter>,
40
41 input_projection: Parameter,
43 output_projection: Parameter,
44
45 bias: Option<Parameter>,
47}
48
49impl GraphFNO {
50 pub fn new(
52 in_features: usize,
53 out_features: usize,
54 hidden_features: usize,
55 num_modes: usize,
56 num_layers: usize,
57 bias: bool,
58 ) -> Result<Self> {
59 let mut fourier_weights = Vec::new();
60 let mut conv_weights = Vec::new();
61
62 for _ in 0..num_layers {
64 fourier_weights.push(Parameter::new(randn(&[
65 hidden_features,
66 hidden_features,
67 num_modes,
68 ])?));
69 conv_weights.push(Parameter::new(randn(&[hidden_features, hidden_features])?));
70 }
71
72 let input_projection = Parameter::new(randn(&[in_features, hidden_features])?);
73 let output_projection = Parameter::new(randn(&[hidden_features, out_features])?);
74
75 let bias = if bias {
76 Some(Parameter::new(zeros::<f32>(&[out_features])?))
77 } else {
78 None
79 };
80
81 Ok(Self {
82 in_features,
83 out_features,
84 hidden_features,
85 num_modes,
86 num_layers,
87 fourier_weights,
88 conv_weights,
89 input_projection,
90 output_projection,
91 bias,
92 })
93 }
94
95 pub fn forward(&self, graph: &GraphData) -> Result<GraphData> {
97 let _num_nodes = graph.num_nodes;
98
99 let mut x = graph.x.matmul(&self.input_projection.clone_data())?;
101
102 for layer in 0..self.num_layers {
104 x = self.fourier_layer(&x, layer, graph)?;
105 }
106
107 let mut output = x.matmul(&self.output_projection.clone_data())?;
109
110 if let Some(ref bias) = self.bias {
112 output = output.add(&bias.clone_data())?;
113 }
114
115 let mut output_graph = graph.clone();
117 output_graph.x = output;
118 Ok(output_graph)
119 }
120
121 fn fourier_layer(&self, x: &Tensor, layer: usize, graph: &GraphData) -> Result<Tensor> {
123 let fourier_x = self.graph_fourier_transform(x, graph)?;
125
126 let fourier_weights = &self.fourier_weights[layer];
128 let spectral_conv = self.spectral_convolution(&fourier_x, fourier_weights)?;
129
130 let spatial_features = self.inverse_graph_fourier_transform(&spectral_conv, graph)?;
132
133 let conv_weights = &self.conv_weights[layer];
135 let conv_output = spatial_features.matmul(&conv_weights.clone_data())?;
136
137 let residual = x.add(&conv_output)?;
139
140 self.relu(&residual)
142 }
143
144 fn graph_fourier_transform(&self, x: &Tensor, graph: &GraphData) -> Result<Tensor> {
146 let num_nodes = graph.num_nodes;
149
150 let mut transform_data = Vec::new();
152 for i in 0..num_nodes {
153 for j in 0..self.num_modes {
154 let freq = (j as f32 + 1.0) * std::f32::consts::PI / num_nodes as f32;
155 let basis = (freq * i as f32).cos();
156 transform_data.push(basis);
157 }
158 }
159
160 let transform_matrix = from_vec(
161 transform_data,
162 &[num_nodes, self.num_modes],
163 torsh_core::device::DeviceType::Cpu,
164 )?;
165
166 Ok(transform_matrix.t()?.matmul(x)?)
168 }
169
170 fn inverse_graph_fourier_transform(
172 &self,
173 fourier_x: &Tensor,
174 graph: &GraphData,
175 ) -> Result<Tensor> {
176 let num_nodes = graph.num_nodes;
177
178 let mut inv_transform_data = Vec::new();
180 for i in 0..num_nodes {
181 for j in 0..self.num_modes {
182 let freq = (j as f32 + 1.0) * std::f32::consts::PI / num_nodes as f32;
183 let basis = (freq * i as f32).cos();
184 inv_transform_data.push(basis);
185 }
186 }
187
188 let inv_transform_matrix = from_vec(
189 inv_transform_data,
190 &[num_nodes, self.num_modes],
191 torsh_core::device::DeviceType::Cpu,
192 )?;
193
194 Ok(inv_transform_matrix.matmul(fourier_x)?)
196 }
197
198 fn spectral_convolution(&self, fourier_x: &Tensor, weights: &Parameter) -> Result<Tensor> {
200 let weight_data = weights.clone_data();
202
203 let weight_2d = weight_data.slice_tensor(2, 0, 1)?.squeeze_tensor(2)?;
206
207 Ok(fourier_x.matmul(&weight_2d)?)
208 }
209
210 fn relu(&self, x: &Tensor) -> Result<Tensor> {
212 let data = x.to_vec()?;
214 let activated_data: Vec<f32> = data.iter().map(|&val| val.max(0.0)).collect();
215
216 Ok(from_vec(
217 activated_data,
218 x.shape().dims(),
219 torsh_core::device::DeviceType::Cpu,
220 )?)
221 }
222}
223
224impl GraphLayer for GraphFNO {
225 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
226 self.forward(graph)
227 }
228
229 fn parameters(&self) -> Vec<Tensor> {
230 let mut params = vec![
231 self.input_projection.clone_data(),
232 self.output_projection.clone_data(),
233 ];
234
235 for weight in &self.fourier_weights {
236 params.push(weight.clone_data());
237 }
238
239 for weight in &self.conv_weights {
240 params.push(weight.clone_data());
241 }
242
243 if let Some(ref bias) = self.bias {
244 params.push(bias.clone_data());
245 }
246
247 params
248 }
249}
250
251#[derive(Debug)]
253pub struct GraphDeepONet {
254 trunk_net_features: usize,
255 branch_net_features: usize,
256 hidden_features: usize,
257 output_features: usize,
258 num_sensors: usize,
259
260 branch_layers: Vec<Parameter>,
262
263 trunk_layers: Vec<Parameter>,
265
266 bias: Option<Parameter>,
268}
269
270impl GraphDeepONet {
271 pub fn new(
273 trunk_net_features: usize,
274 branch_net_features: usize,
275 hidden_features: usize,
276 output_features: usize,
277 num_sensors: usize,
278 num_layers: usize,
279 bias: bool,
280 ) -> Result<Self> {
281 let mut branch_layers = Vec::new();
282 let mut trunk_layers = Vec::new();
283
284 for i in 0..num_layers {
286 let in_dim = if i == 0 { num_sensors } else { hidden_features };
287 let out_dim = if i == num_layers - 1 {
288 output_features
289 } else {
290 hidden_features
291 };
292 branch_layers.push(Parameter::new(randn(&[in_dim, out_dim])?));
293 }
294
295 for i in 0..num_layers {
297 let in_dim = if i == 0 {
298 trunk_net_features
299 } else {
300 hidden_features
301 };
302 let out_dim = if i == num_layers - 1 {
303 output_features
304 } else {
305 hidden_features
306 };
307 trunk_layers.push(Parameter::new(randn(&[in_dim, out_dim])?));
308 }
309
310 let bias = if bias {
311 Some(Parameter::new(zeros::<f32>(&[output_features])?))
312 } else {
313 None
314 };
315
316 Ok(Self {
317 trunk_net_features,
318 branch_net_features,
319 hidden_features,
320 output_features,
321 num_sensors,
322 branch_layers,
323 trunk_layers,
324 bias,
325 })
326 }
327
328 pub fn forward(
330 &self,
331 graph: &GraphData,
332 sensor_data: &Tensor,
333 locations: &Tensor,
334 ) -> Result<GraphData> {
335 let branch_output = self.forward_branch_net(sensor_data)?;
337
338 let trunk_output = self.forward_trunk_net(locations)?;
340
341 let combined = self.combine_outputs(&branch_output, &trunk_output)?;
343
344 let mut output = combined;
346 if let Some(ref bias) = self.bias {
347 output = output.add(&bias.clone_data())?;
348 }
349
350 let mut output_graph = graph.clone();
352 output_graph.x = output;
353 Ok(output_graph)
354 }
355
356 fn forward_branch_net(&self, sensor_data: &Tensor) -> Result<Tensor> {
358 let mut x = sensor_data.clone();
359
360 for (i, layer) in self.branch_layers.iter().enumerate() {
361 x = x.matmul(&layer.clone_data())?;
362
363 if i < self.branch_layers.len() - 1 {
365 x = self.tanh(&x)?;
366 }
367 }
368
369 Ok(x)
370 }
371
372 fn forward_trunk_net(&self, locations: &Tensor) -> Result<Tensor> {
374 let mut x = locations.clone();
375
376 for (i, layer) in self.trunk_layers.iter().enumerate() {
377 x = x.matmul(&layer.clone_data())?;
378
379 if i < self.trunk_layers.len() - 1 {
381 x = self.tanh(&x)?;
382 }
383 }
384
385 Ok(x)
386 }
387
388 fn combine_outputs(&self, branch_output: &Tensor, trunk_output: &Tensor) -> Result<Tensor> {
390 Ok(branch_output.mul(trunk_output)?)
392 }
393
394 fn tanh(&self, x: &Tensor) -> Result<Tensor> {
396 let data = x.to_vec()?;
397 let activated_data: Vec<f32> = data.iter().map(|&val| val.tanh()).collect();
398
399 Ok(from_vec(
400 activated_data,
401 x.shape().dims(),
402 torsh_core::device::DeviceType::Cpu,
403 )?)
404 }
405}
406
407impl GraphLayer for GraphDeepONet {
408 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
409 let sensor_data =
411 graph
412 .x
413 .slice_tensor(1, 0, self.num_sensors.min(graph.x.shape().dims()[1]))?;
414 let locations = graph.x.clone();
415
416 self.forward(graph, &sensor_data, &locations)
417 }
418
419 fn parameters(&self) -> Vec<Tensor> {
420 let mut params = Vec::new();
421
422 for layer in &self.branch_layers {
423 params.push(layer.clone_data());
424 }
425
426 for layer in &self.trunk_layers {
427 params.push(layer.clone_data());
428 }
429
430 if let Some(ref bias) = self.bias {
431 params.push(bias.clone_data());
432 }
433
434 params
435 }
436}
437
438#[derive(Debug)]
440pub struct PhysicsInformedGNN {
441 in_features: usize,
442 out_features: usize,
443 hidden_features: usize,
444
445 layers: Vec<Parameter>,
447
448 diffusion_coefficient: f32,
450 reaction_rate: f32,
451
452 bias: Option<Parameter>,
454}
455
456impl PhysicsInformedGNN {
457 pub fn new(
459 in_features: usize,
460 out_features: usize,
461 hidden_features: usize,
462 num_layers: usize,
463 diffusion_coefficient: f32,
464 reaction_rate: f32,
465 bias: bool,
466 ) -> Result<Self> {
467 let mut layers = Vec::new();
468
469 for i in 0..num_layers {
470 let in_dim = if i == 0 { in_features } else { hidden_features };
471 let out_dim = if i == num_layers - 1 {
472 out_features
473 } else {
474 hidden_features
475 };
476 layers.push(Parameter::new(randn(&[in_dim, out_dim])?));
477 }
478
479 let bias = if bias {
480 Some(Parameter::new(zeros::<f32>(&[out_features])?))
481 } else {
482 None
483 };
484
485 Ok(Self {
486 in_features,
487 out_features,
488 hidden_features,
489 layers,
490 diffusion_coefficient,
491 reaction_rate,
492 bias,
493 })
494 }
495
496 pub fn forward(&self, graph: &GraphData) -> Result<GraphData> {
498 let mut x = graph.x.clone();
500
501 for (i, layer) in self.layers.iter().enumerate() {
502 x = x.matmul(&layer.clone_data())?;
503
504 if i < self.layers.len() - 1 {
506 x = self.swish(&x)?;
507 }
508 }
509
510 let physics_constrained = self.apply_physics_constraints(&x, graph);
512
513 let mut output = physics_constrained;
515 if let Some(ref bias) = self.bias {
516 output = Ok(output?.add(&bias.clone_data())?);
517 }
518
519 let mut output_graph = graph.clone();
521 output_graph.x = output?;
522 Ok(output_graph)
523 }
524
525 fn apply_physics_constraints(&self, prediction: &Tensor, graph: &GraphData) -> Result<Tensor> {
527 let laplacian = self.compute_graph_laplacian(graph);
529
530 let diffusion_term = laplacian?
532 .matmul(prediction)?
533 .mul_scalar(self.diffusion_coefficient)?;
534
535 let reaction_term = prediction.mul_scalar(self.reaction_rate)?;
537
538 Ok(prediction.add(&diffusion_term)?.add(&reaction_term)?)
540 }
541
542 fn compute_graph_laplacian(&self, graph: &GraphData) -> Result<Tensor> {
544 let num_nodes = graph.num_nodes;
545 let _num_edges = graph.num_edges;
546
547 let mut adj_data = vec![0.0f32; num_nodes * num_nodes];
549
550 let edge_data = graph.edge_index.to_vec()?;
552 for i in (0..edge_data.len()).step_by(2) {
553 if i + 1 < edge_data.len() {
554 let src = edge_data[i] as usize;
555 let dst = edge_data[i + 1] as usize;
556
557 if src < num_nodes && dst < num_nodes {
558 adj_data[src * num_nodes + dst] = 1.0;
559 adj_data[dst * num_nodes + src] = 1.0; }
561 }
562 }
563
564 let mut degree_data = vec![0.0f32; num_nodes * num_nodes];
566 for i in 0..num_nodes {
567 let mut degree = 0.0;
568 for j in 0..num_nodes {
569 degree += adj_data[i * num_nodes + j];
570 }
571 degree_data[i * num_nodes + i] = degree;
572 }
573
574 let mut laplacian_data = Vec::new();
576 for i in 0..num_nodes * num_nodes {
577 laplacian_data.push(degree_data[i] - adj_data[i]);
578 }
579
580 Ok(from_vec(
581 laplacian_data,
582 &[num_nodes, num_nodes],
583 torsh_core::device::DeviceType::Cpu,
584 )?)
585 }
586
587 fn swish(&self, x: &Tensor) -> Result<Tensor> {
589 let data = x.to_vec()?;
590 let activated_data: Vec<f32> = data
591 .iter()
592 .map(|&val| val * (1.0 / (1.0 + (-val).exp())))
593 .collect();
594
595 Ok(from_vec(
596 activated_data,
597 x.shape().dims(),
598 torsh_core::device::DeviceType::Cpu,
599 )?)
600 }
601}
602
603impl GraphLayer for PhysicsInformedGNN {
604 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
605 self.forward(graph)
606 }
607
608 fn parameters(&self) -> Vec<Tensor> {
609 let mut params = Vec::new();
610
611 for layer in &self.layers {
612 params.push(layer.clone_data());
613 }
614
615 if let Some(ref bias) = self.bias {
616 params.push(bias.clone_data());
617 }
618
619 params
620 }
621}
622
623#[derive(Debug)]
625pub struct MultiScaleGNO {
626 in_features: usize,
627 out_features: usize,
628 num_scales: usize,
629 hidden_features: usize,
630
631 scale_operators: Vec<Parameter>,
633
634 fusion_weights: Parameter,
636
637 output_projection: Parameter,
639
640 bias: Option<Parameter>,
641}
642
643impl MultiScaleGNO {
644 pub fn new(
646 in_features: usize,
647 out_features: usize,
648 num_scales: usize,
649 hidden_features: usize,
650 bias: bool,
651 ) -> Result<Self> {
652 let mut scale_operators = Vec::new();
653
654 for _ in 0..num_scales {
656 scale_operators.push(Parameter::new(randn(&[in_features, hidden_features])?));
657 }
658
659 let fusion_weights =
660 Parameter::new(randn(&[num_scales * hidden_features, hidden_features])?);
661
662 let output_projection = Parameter::new(randn(&[hidden_features, out_features])?);
663
664 let bias = if bias {
665 Some(Parameter::new(zeros::<f32>(&[out_features])?))
666 } else {
667 None
668 };
669
670 Ok(Self {
671 in_features,
672 out_features,
673 num_scales,
674 hidden_features,
675 scale_operators,
676 fusion_weights,
677 output_projection,
678 bias,
679 })
680 }
681
682 pub fn forward(&self, graph: &GraphData) -> Result<GraphData> {
684 let mut scale_features = Vec::new();
685
686 for scale in 0..self.num_scales {
688 let scale_graph = self.coarsen_graph(graph, scale)?;
689 let features = self.process_scale(&scale_graph, scale)?;
690 let upsampled = self.upsample_features(&features, graph.num_nodes)?;
691 scale_features.push(upsampled);
692 }
693
694 let fused_features = self.fuse_scales(&scale_features)?;
696
697 let mut output = fused_features.matmul(&self.output_projection.clone_data())?;
699
700 if let Some(ref bias) = self.bias {
702 output = output.add(&bias.clone_data())?;
703 }
704
705 let mut output_graph = graph.clone();
707 output_graph.x = output;
708 Ok(output_graph)
709 }
710
711 fn coarsen_graph(&self, graph: &GraphData, scale: usize) -> Result<GraphData> {
713 let coarsening_factor = 2_usize.pow(scale as u32);
714 let coarse_nodes = (graph.num_nodes + coarsening_factor - 1) / coarsening_factor;
715
716 let mut coarse_features = Vec::new();
718
719 for coarse_id in 0..coarse_nodes {
720 let start_node = coarse_id * coarsening_factor;
721 let end_node = ((coarse_id + 1) * coarsening_factor).min(graph.num_nodes);
722
723 let mut sum_features = vec![0.0f32; graph.x.shape().dims()[1]];
725 let mut count = 0;
726
727 for node_id in start_node..end_node {
728 let features = graph.x.slice_tensor(0, node_id, node_id + 1)?;
729 let feature_data = features.to_vec()?;
730
731 for (i, &val) in feature_data.iter().enumerate() {
732 if i < sum_features.len() {
733 sum_features[i] += val;
734 }
735 }
736 count += 1;
737 }
738
739 if count > 0 {
741 for val in &mut sum_features {
742 *val /= count as f32;
743 }
744 }
745
746 coarse_features.extend(sum_features);
747 }
748
749 let coarse_x = from_vec(
750 coarse_features,
751 &[coarse_nodes, graph.x.shape().dims()[1]],
752 torsh_core::device::DeviceType::Cpu,
753 )?;
754
755 let mut coarse_edges = Vec::new();
757 for i in 0..coarse_nodes.saturating_sub(1) {
758 coarse_edges.push(i as f32);
759 coarse_edges.push((i + 1) as f32);
760 }
761
762 let coarse_edge_index = from_vec(
763 coarse_edges,
764 &[2, coarse_nodes.saturating_sub(1)],
765 torsh_core::device::DeviceType::Cpu,
766 )?;
767
768 Ok(GraphData::new(coarse_x, coarse_edge_index))
769 }
770
771 fn process_scale(&self, graph: &GraphData, scale: usize) -> Result<Tensor> {
773 let operator = &self.scale_operators[scale];
774 Ok(graph.x.matmul(&operator.clone_data())?)
775 }
776
777 fn upsample_features(&self, features: &Tensor, target_nodes: usize) -> Result<Tensor> {
779 let current_nodes = features.shape().dims()[0];
780 let feature_dim = features.shape().dims()[1];
781
782 if current_nodes >= target_nodes {
783 return Ok(features.slice_tensor(0, 0, target_nodes)?);
785 }
786
787 let feature_data = features.to_vec()?;
789 let mut upsampled_data = Vec::new();
790
791 for target_id in 0..target_nodes {
792 let source_id = (target_id * current_nodes) / target_nodes;
793 let start_idx = source_id * feature_dim;
794 let end_idx = start_idx + feature_dim;
795
796 if end_idx <= feature_data.len() {
797 upsampled_data.extend(&feature_data[start_idx..end_idx]);
798 } else {
799 upsampled_data.extend(vec![0.0f32; feature_dim]);
801 }
802 }
803
804 Ok(from_vec(
805 upsampled_data,
806 &[target_nodes, feature_dim],
807 torsh_core::device::DeviceType::Cpu,
808 )?)
809 }
810
811 fn fuse_scales(&self, scale_features: &[Tensor]) -> Result<Tensor> {
813 let mut concatenated_data = Vec::new();
815 let num_nodes = scale_features[0].shape().dims()[0];
816
817 for node_id in 0..num_nodes {
818 for scale_feature in scale_features {
819 let node_features = scale_feature.slice_tensor(0, node_id, node_id + 1)?;
820 let feature_data = node_features.to_vec()?;
821 concatenated_data.extend(feature_data);
822 }
823 }
824
825 let concatenated = from_vec(
826 concatenated_data,
827 &[num_nodes, self.num_scales * self.hidden_features],
828 torsh_core::device::DeviceType::Cpu,
829 )?;
830
831 Ok(concatenated.matmul(&self.fusion_weights.clone_data())?)
833 }
834}
835
836impl GraphLayer for MultiScaleGNO {
837 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
838 self.forward(graph)
839 }
840
841 fn parameters(&self) -> Vec<Tensor> {
842 let mut params = vec![
843 self.fusion_weights.clone_data(),
844 self.output_projection.clone_data(),
845 ];
846
847 for operator in &self.scale_operators {
848 params.push(operator.clone_data());
849 }
850
851 if let Some(ref bias) = self.bias {
852 params.push(bias.clone_data());
853 }
854
855 params
856 }
857}
858
859pub mod utils {
861 use super::*;
862
863 pub fn compute_spectral_features(graph: &GraphData, num_eigenvalues: usize) -> Result<Tensor> {
865 let num_nodes = graph.num_nodes;
867 let mut spectral_data = Vec::new();
868
869 for i in 0..num_nodes {
870 for j in 0..num_eigenvalues {
871 let eigenvalue = (j as f32 + 1.0) / num_eigenvalues as f32;
872 let eigenvector_val = (std::f32::consts::PI * (i as f32 + 1.0) * (j as f32 + 1.0)
873 / num_nodes as f32)
874 .sin();
875 spectral_data.push(eigenvalue * eigenvector_val);
876 }
877 }
878
879 Ok(from_vec(
880 spectral_data,
881 &[num_nodes, num_eigenvalues],
882 torsh_core::device::DeviceType::Cpu,
883 )?)
884 }
885
886 pub fn generate_operator_data(
888 num_graphs: usize,
889 num_nodes: usize,
890 feature_dim: usize,
891 ) -> Result<Vec<(GraphData, GraphData)>> {
892 let mut rng = scirs2_core::random::thread_rng();
893 let mut data_pairs = Vec::new();
894
895 for _ in 0..num_graphs {
896 let input_features = randn(&[num_nodes, feature_dim])?;
898 let mut edge_data = Vec::new();
899
900 for _ in 0..(num_nodes * 2) {
902 let src = rng.gen_range(0..num_nodes) as f32;
903 let dst = rng.gen_range(0..num_nodes) as f32;
904 edge_data.push(src);
905 edge_data.push(dst);
906 }
907
908 let edge_index = from_vec(
909 edge_data,
910 &[2, num_nodes * 2],
911 torsh_core::device::DeviceType::Cpu,
912 )?;
913
914 let input_graph = GraphData::new(input_features, edge_index);
915
916 let output_features = input_graph.x.mul_scalar(2.0)?;
918 let output_graph = GraphData::new(output_features, input_graph.edge_index.clone());
919
920 data_pairs.push((input_graph, output_graph));
921 }
922
923 Ok(data_pairs)
924 }
925
926 pub fn compute_operator_error(predicted: &GraphData, target: &GraphData) -> Result<f32> {
928 let pred_data = predicted.x.to_vec()?;
929 let target_data = target.x.to_vec()?;
930
931 let mut mse = 0.0;
932 let mut count = 0;
933
934 for (pred, target) in pred_data.iter().zip(target_data.iter()) {
935 mse += (pred - target).powi(2);
936 count += 1;
937 }
938
939 if count > 0 {
940 Ok(mse / count as f32)
941 } else {
942 Ok(0.0)
943 }
944 }
945}
946
947#[cfg(test)]
948mod tests {
949 use super::*;
950 use torsh_core::device::DeviceType;
951
952 #[test]
953 fn test_graph_fno_creation() {
954 let fno = GraphFNO::new(4, 8, 16, 10, 3, true).expect("operation should succeed");
955 assert_eq!(fno.in_features, 4);
956 assert_eq!(fno.out_features, 8);
957 assert_eq!(fno.hidden_features, 16);
958 assert_eq!(fno.num_modes, 10);
959 assert_eq!(fno.num_layers, 3);
960 }
961
962 #[test]
963 fn test_graph_fno_forward() {
964 let features = randn(&[5, 4]).unwrap();
965 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0];
966 let edge_index = from_vec(edges, &[2, 4], DeviceType::Cpu).unwrap();
967 let graph = GraphData::new(features, edge_index);
968
969 let fno = GraphFNO::new(4, 8, 16, 10, 3, true).expect("operation should succeed");
970 let output = fno.forward(&graph).expect("operation should succeed");
971
972 assert_eq!(output.x.shape().dims(), &[5, 8]);
973 }
974
975 #[test]
976 fn test_graph_deeponet_creation() {
977 let deeponet =
978 GraphDeepONet::new(3, 4, 16, 8, 10, 3, true).expect("operation should succeed");
979 assert_eq!(deeponet.trunk_net_features, 3);
980 assert_eq!(deeponet.branch_net_features, 4);
981 assert_eq!(deeponet.output_features, 8);
982 assert_eq!(deeponet.num_sensors, 10);
983 }
984
985 #[test]
986 fn test_physics_informed_gnn() {
987 let features = randn(&[4, 3]).unwrap();
988 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0];
989 let edge_index = from_vec(edges, &[2, 3], DeviceType::Cpu).unwrap();
990 let graph = GraphData::new(features, edge_index);
991
992 let pignn = PhysicsInformedGNN::new(3, 6, 12, 2, 0.1, 0.05, true)
993 .expect("operation should succeed");
994 let output = pignn.forward(&graph).expect("operation should succeed");
995
996 assert_eq!(output.x.shape().dims(), &[4, 6]);
997 }
998
999 #[test]
1000 fn test_multi_scale_gno() {
1001 let features = randn(&[8, 4]).unwrap();
1002 let edges = vec![
1003 0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 6.0, 6.0, 7.0,
1004 ];
1005 let edge_index = from_vec(edges, &[2, 7], DeviceType::Cpu).unwrap();
1006 let graph = GraphData::new(features, edge_index);
1007
1008 let ms_gno = MultiScaleGNO::new(4, 6, 3, 8, true).expect("operation should succeed");
1009 let output = ms_gno.forward(&graph).expect("operation should succeed");
1010
1011 assert_eq!(output.x.shape().dims(), &[8, 6]);
1012 }
1013
1014 #[test]
1015 fn test_spectral_features() {
1016 let features = randn(&[6, 3]).unwrap();
1017 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0, 5.0];
1018 let edge_index = from_vec(edges, &[2, 5], DeviceType::Cpu).unwrap();
1019 let graph = GraphData::new(features, edge_index);
1020
1021 let spectral_features =
1022 utils::compute_spectral_features(&graph, 4).expect("operation should succeed");
1023 assert_eq!(spectral_features.shape().dims(), &[6, 4]);
1024 }
1025
1026 #[test]
1027 fn test_operator_data_generation() {
1028 let data_pairs = utils::generate_operator_data(3, 5, 4).expect("operation should succeed");
1029 assert_eq!(data_pairs.len(), 3);
1030
1031 for (input, output) in &data_pairs {
1032 assert_eq!(input.num_nodes, 5);
1033 assert_eq!(output.num_nodes, 5);
1034 assert_eq!(input.x.shape().dims()[1], 4);
1035 assert_eq!(output.x.shape().dims()[1], 4);
1036 }
1037 }
1038
1039 #[test]
1040 fn test_operator_error_computation() {
1041 let features1 = from_vec(vec![1.0, 2.0, 3.0, 4.0], &[2, 2], DeviceType::Cpu).unwrap();
1042 let features2 = from_vec(vec![1.1, 2.1, 3.1, 4.1], &[2, 2], DeviceType::Cpu).unwrap();
1043 let edges = vec![0.0, 1.0];
1044 let edge_index = from_vec(edges, &[2, 1], DeviceType::Cpu).unwrap();
1045
1046 let graph1 = GraphData::new(features1, edge_index.clone());
1047 let graph2 = GraphData::new(features2, edge_index);
1048
1049 let error =
1050 utils::compute_operator_error(&graph1, &graph2).expect("operation should succeed");
1051 assert!(error > 0.0);
1052 assert!(error < 1.0); }
1054}