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 scirs2_core::random::thread_rng;
23use torsh_tensor::{
24 creation::{from_vec, randn, zeros},
25 Tensor,
26};
27
28fn softplus(x: f32) -> f32 {
33 if x > 0.0 {
34 x + (-x).exp().ln_1p()
35 } else {
36 x.exp().ln_1p()
37 }
38}
39
40#[derive(Debug)]
43pub struct GraphVAE {
44 encoder_in_features: usize,
46 encoder_hidden_features: usize,
47 latent_dim: usize,
48
49 encoder_layer1: Parameter,
51 encoder_layer2: Parameter,
52
53 mu_layer: Parameter,
55 logvar_layer: Parameter,
56
57 decoder_layer1: Parameter,
59 decoder_layer2: Parameter,
60 node_decoder: Parameter,
61 edge_decoder: Parameter,
62
63 beta: f32,
65
66 encoder_bias1: Option<Parameter>,
68 encoder_bias2: Option<Parameter>,
69 decoder_bias1: Option<Parameter>,
70 decoder_bias2: Option<Parameter>,
71}
72
73impl GraphVAE {
74 pub fn new(
76 in_features: usize,
77 hidden_features: usize,
78 latent_dim: usize,
79 beta: f32,
80 use_bias: bool,
81 ) -> Result<Self> {
82 let encoder_layer1 = Parameter::new(randn(&[in_features, hidden_features])?);
84 let encoder_layer2 = Parameter::new(randn(&[hidden_features, hidden_features])?);
85
86 let mu_layer = Parameter::new(randn(&[hidden_features, latent_dim])?);
88 let logvar_layer = Parameter::new(randn(&[hidden_features, latent_dim])?);
89
90 let decoder_layer1 = Parameter::new(randn(&[latent_dim, hidden_features])?);
92 let decoder_layer2 = Parameter::new(randn(&[hidden_features, hidden_features])?);
93 let node_decoder = Parameter::new(randn(&[hidden_features, in_features])?);
94 let edge_decoder = Parameter::new(randn(&[hidden_features, 1])?);
95
96 let (encoder_bias1, encoder_bias2, decoder_bias1, decoder_bias2) = if use_bias {
97 (
98 Some(Parameter::new(zeros(&[hidden_features])?)),
99 Some(Parameter::new(zeros(&[hidden_features])?)),
100 Some(Parameter::new(zeros(&[hidden_features])?)),
101 Some(Parameter::new(zeros(&[hidden_features])?)),
102 )
103 } else {
104 (None, None, None, None)
105 };
106
107 Ok(Self {
108 encoder_in_features: in_features,
109 encoder_hidden_features: hidden_features,
110 latent_dim,
111 encoder_layer1,
112 encoder_layer2,
113 mu_layer,
114 logvar_layer,
115 decoder_layer1,
116 decoder_layer2,
117 node_decoder,
118 edge_decoder,
119 beta,
120 encoder_bias1,
121 encoder_bias2,
122 decoder_bias1,
123 decoder_bias2,
124 })
125 }
126
127 pub fn encode(&self, graph: &GraphData) -> Result<(Tensor, Tensor)> {
129 let mut h = graph.x.matmul(&self.encoder_layer1.clone_data())?;
131 if let Some(ref bias) = self.encoder_bias1 {
132 h = h.add(&bias.clone_data())?;
133 }
134 h = self.relu(&h)?;
135
136 h = h.matmul(&self.encoder_layer2.clone_data())?;
137 if let Some(ref bias) = self.encoder_bias2 {
138 h = h.add(&bias.clone_data())?;
139 }
140 h = self.relu(&h)?;
141
142 let graph_embedding = h.mean(Some(&[0]), false)?;
144 let graph_embedding_2d = graph_embedding.unsqueeze(0)?; let mu = graph_embedding_2d.matmul(&self.mu_layer.clone_data())?;
148 let logvar = graph_embedding_2d.matmul(&self.logvar_layer.clone_data())?;
149
150 Ok((mu, logvar))
151 }
152
153 pub fn reparameterize(&self, mu: &Tensor, logvar: &Tensor) -> Result<Tensor> {
155 let std = logvar.mul_scalar(0.5)?.exp()?;
157
158 let epsilon = randn(mu.shape().dims())?;
160
161 Ok(mu.add(&std.mul(&epsilon)?)?)
163 }
164
165 pub fn decode(&self, z: &Tensor, num_nodes: usize) -> Result<GraphData> {
167 let mut h = z.matmul(&self.decoder_layer1.clone_data())?;
169 if let Some(ref bias) = self.decoder_bias1 {
170 h = h.add(&bias.clone_data())?;
171 }
172 h = self.relu(&h)?;
173
174 h = h.matmul(&self.decoder_layer2.clone_data())?;
175 if let Some(ref bias) = self.decoder_bias2 {
176 h = h.add(&bias.clone_data())?;
177 }
178 h = self.relu(&h)?;
179
180 let h_expanded = self.expand_to_nodes(&h, num_nodes)?;
182
183 let node_features = h_expanded.matmul(&self.node_decoder.clone_data())?;
185
186 let edge_logits = self.decode_edges(&h_expanded, num_nodes)?;
188 let edge_index = self.sample_edges(&edge_logits, num_nodes)?;
189
190 Ok(GraphData::new(node_features, edge_index))
191 }
192
193 pub fn forward(&self, graph: &GraphData) -> Result<(GraphData, Tensor, Tensor)> {
198 let (mu, logvar) = self.encode(graph)?;
200
201 let z = self.reparameterize(&mu, &logvar)?;
203
204 let reconstructed = self.decode(&z, graph.num_nodes)?;
206
207 Ok((reconstructed, mu, logvar))
208 }
209
210 pub fn compute_loss(
212 &self,
213 graph: &GraphData,
214 reconstructed: &GraphData,
215 mu: &Tensor,
216 logvar: &Tensor,
217 ) -> Result<f32> {
218 let recon_loss = self.reconstruction_loss(graph, reconstructed)?;
220
221 let kl_loss = self.kl_divergence(mu, logvar)?;
223
224 Ok(recon_loss + self.beta * kl_loss)
226 }
227
228 fn reconstruction_loss(&self, original: &GraphData, reconstructed: &GraphData) -> Result<f32> {
230 let orig_data = original.x.to_vec()?;
231 let recon_data = reconstructed.x.to_vec()?;
232
233 let mut mse = 0.0;
234 let len = orig_data.len().min(recon_data.len());
235
236 for i in 0..len {
237 mse += (orig_data[i] - recon_data[i]).powi(2);
238 }
239
240 Ok(mse / len as f32)
241 }
242
243 fn kl_divergence(&self, mu: &Tensor, logvar: &Tensor) -> Result<f32> {
245 let mu_data = mu.to_vec()?;
246 let logvar_data = logvar.to_vec()?;
247
248 let mut kl = 0.0;
249 for i in 0..mu_data.len() {
250 kl += -0.5 * (1.0 + logvar_data[i] - mu_data[i].powi(2) - logvar_data[i].exp());
251 }
252
253 Ok(kl / mu_data.len() as f32)
254 }
255
256 pub fn generate(&self, num_nodes: usize) -> Result<GraphData> {
258 let z = randn(&[1, self.latent_dim])?;
260
261 self.decode(&z, num_nodes)
263 }
264
265 pub fn interpolate(
267 &self,
268 graph1: &GraphData,
269 graph2: &GraphData,
270 alpha: f32,
271 num_nodes: usize,
272 ) -> Result<GraphData> {
273 let (mu1, _) = self.encode(graph1)?;
274 let (mu2, _) = self.encode(graph2)?;
275
276 let z_interp = mu1.mul_scalar(1.0 - alpha)?.add(&mu2.mul_scalar(alpha)?)?;
278
279 self.decode(&z_interp, num_nodes)
281 }
282
283 fn relu(&self, x: &Tensor) -> Result<Tensor> {
286 let data = x.to_vec()?;
287 let activated: Vec<f32> = data.iter().map(|&v| v.max(0.0)).collect();
288 Ok(from_vec(
289 activated,
290 x.shape().dims(),
291 torsh_core::device::DeviceType::Cpu,
292 )?)
293 }
294
295 fn expand_to_nodes(&self, h: &Tensor, num_nodes: usize) -> Result<Tensor> {
296 let h_data = h.to_vec()?;
298 let feat_dim = h_data.len();
299
300 let mut expanded_data = Vec::new();
301 for _ in 0..num_nodes {
302 expanded_data.extend(&h_data);
303 }
304
305 Ok(from_vec(
306 expanded_data,
307 &[num_nodes, feat_dim],
308 torsh_core::device::DeviceType::Cpu,
309 )?)
310 }
311
312 fn decode_edges(&self, h: &Tensor, num_nodes: usize) -> Result<Tensor> {
313 let mut edge_logits_data = Vec::new();
315
316 for i in 0..num_nodes {
317 for j in 0..num_nodes {
318 if i != j {
319 let h_i = h.slice_tensor(0, i, i + 1)?;
321 let h_j = h.slice_tensor(0, j, j + 1)?;
322
323 let logit = h_i.dot(&h_j.t()?)?.item()?;
324 edge_logits_data.push(logit);
325 } else {
326 edge_logits_data.push(-1000.0); }
328 }
329 }
330
331 Ok(from_vec(
332 edge_logits_data,
333 &[num_nodes, num_nodes],
334 torsh_core::device::DeviceType::Cpu,
335 )?)
336 }
337
338 fn sample_edges(&self, edge_logits: &Tensor, num_nodes: usize) -> Result<Tensor> {
339 let logits_data = edge_logits.to_vec()?;
340 let mut edges = Vec::new();
341
342 for i in 0..num_nodes {
344 for j in 0..num_nodes {
345 if i != j {
346 let idx = i * num_nodes + j;
347 let prob = 1.0 / (1.0 + (-logits_data[idx]).exp()); if prob > 0.5 {
350 edges.push(i as f32);
351 edges.push(j as f32);
352 }
353 }
354 }
355 }
356
357 if edges.is_empty() {
358 return Ok(zeros(&[2, 0])?);
360 }
361
362 let num_edges = edges.len() / 2;
363 Ok(from_vec(
364 edges,
365 &[2, num_edges],
366 torsh_core::device::DeviceType::Cpu,
367 )?)
368 }
369}
370
371impl GraphLayer for GraphVAE {
372 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
373 let (reconstructed, _, _) = GraphVAE::forward(self, graph)?;
374 Ok(reconstructed)
375 }
376
377 fn parameters(&self) -> Vec<Tensor> {
378 let mut params = vec![
379 self.encoder_layer1.clone_data(),
380 self.encoder_layer2.clone_data(),
381 self.mu_layer.clone_data(),
382 self.logvar_layer.clone_data(),
383 self.decoder_layer1.clone_data(),
384 self.decoder_layer2.clone_data(),
385 self.node_decoder.clone_data(),
386 self.edge_decoder.clone_data(),
387 ];
388
389 if let Some(ref b) = self.encoder_bias1 {
390 params.push(b.clone_data());
391 }
392 if let Some(ref b) = self.encoder_bias2 {
393 params.push(b.clone_data());
394 }
395 if let Some(ref b) = self.decoder_bias1 {
396 params.push(b.clone_data());
397 }
398 if let Some(ref b) = self.decoder_bias2 {
399 params.push(b.clone_data());
400 }
401
402 params
403 }
404}
405
406#[derive(Debug)]
409pub struct GraphGAN {
410 latent_dim: usize,
411 hidden_dim: usize,
412 output_features: usize,
413
414 generator: GraphGANGenerator,
416
417 discriminator: GraphGANDiscriminator,
419}
420
421impl GraphGAN {
422 pub fn new(
424 latent_dim: usize,
425 hidden_dim: usize,
426 output_features: usize,
427 use_bias: bool,
428 ) -> Result<Self> {
429 let generator = GraphGANGenerator::new(latent_dim, hidden_dim, output_features, use_bias)?;
430 let discriminator = GraphGANDiscriminator::new(output_features, hidden_dim, use_bias)?;
431
432 Ok(Self {
433 latent_dim,
434 hidden_dim,
435 output_features,
436 generator,
437 discriminator,
438 })
439 }
440
441 pub fn generate(&self, num_nodes: usize) -> Result<GraphData> {
443 let z = randn(&[1, self.latent_dim])?;
444 self.generator.generate(&z, num_nodes)
445 }
446
447 pub fn discriminate(&self, graph: &GraphData) -> Result<f32> {
452 self.discriminator.forward(graph)
453 }
454
455 pub fn discriminate_logit(&self, graph: &GraphData) -> Result<f32> {
464 self.discriminator.forward_logit(graph)
465 }
466
467 pub fn generator_loss(&self, num_nodes: usize) -> Result<f32> {
475 let fake_graph = self.generate(num_nodes)?;
476 let fake_logit = self.discriminate_logit(&fake_graph)?;
477
478 Ok(softplus(-fake_logit))
480 }
481
482 pub fn discriminator_loss(&self, real_graph: &GraphData, num_nodes: usize) -> Result<f32> {
491 let real_logit = self.discriminate_logit(real_graph)?;
492
493 let fake_graph = self.generate(num_nodes)?;
494 let fake_logit = self.discriminate_logit(&fake_graph)?;
495
496 Ok(softplus(-real_logit) + softplus(fake_logit))
499 }
500
501 pub fn generator_parameters(&self) -> Vec<Tensor> {
503 self.generator.parameters()
504 }
505
506 pub fn discriminator_parameters(&self) -> Vec<Tensor> {
508 self.discriminator.parameters()
509 }
510}
511
512#[derive(Debug)]
514struct GraphGANGenerator {
515 latent_dim: usize,
516 hidden_dim: usize,
517 output_features: usize,
518
519 layer1: Parameter,
520 layer2: Parameter,
521 node_layer: Parameter,
522 edge_layer: Parameter,
523
524 bias1: Option<Parameter>,
525 bias2: Option<Parameter>,
526}
527
528impl GraphGANGenerator {
529 fn new(
530 latent_dim: usize,
531 hidden_dim: usize,
532 output_features: usize,
533 use_bias: bool,
534 ) -> Result<Self> {
535 let layer1 = Parameter::new(randn(&[latent_dim, hidden_dim])?);
536 let layer2 = Parameter::new(randn(&[hidden_dim, hidden_dim])?);
537 let node_layer = Parameter::new(randn(&[hidden_dim, output_features])?);
538 let edge_layer = Parameter::new(randn(&[hidden_dim, 1])?);
539
540 let (bias1, bias2) = if use_bias {
541 (
542 Some(Parameter::new(zeros(&[hidden_dim])?)),
543 Some(Parameter::new(zeros(&[hidden_dim])?)),
544 )
545 } else {
546 (None, None)
547 };
548
549 Ok(Self {
550 latent_dim,
551 hidden_dim,
552 output_features,
553 layer1,
554 layer2,
555 node_layer,
556 edge_layer,
557 bias1,
558 bias2,
559 })
560 }
561
562 fn generate(&self, z: &Tensor, num_nodes: usize) -> Result<GraphData> {
563 let mut h = z.matmul(&self.layer1.clone_data())?;
565 if let Some(ref bias) = self.bias1 {
566 h = h.add(&bias.clone_data())?;
567 }
568 h = self.leaky_relu(&h, 0.2)?;
569
570 h = h.matmul(&self.layer2.clone_data())?;
571 if let Some(ref bias) = self.bias2 {
572 h = h.add(&bias.clone_data())?;
573 }
574 h = self.leaky_relu(&h, 0.2)?;
575
576 let h_expanded = self.expand_to_nodes(&h, num_nodes)?;
578
579 let node_features = h_expanded.matmul(&self.node_layer.clone_data())?;
581 let node_features = self.tanh(&node_features)?;
582
583 let edge_index = self.generate_edges(&h_expanded, num_nodes)?;
585
586 Ok(GraphData::new(node_features, edge_index))
587 }
588
589 fn leaky_relu(&self, x: &Tensor, alpha: f32) -> Result<Tensor> {
590 let data = x.to_vec()?;
591 let activated: Vec<f32> = data
592 .iter()
593 .map(|&v| if v > 0.0 { v } else { alpha * v })
594 .collect();
595 Ok(from_vec(
596 activated,
597 x.shape().dims(),
598 torsh_core::device::DeviceType::Cpu,
599 )?)
600 }
601
602 fn tanh(&self, x: &Tensor) -> Result<Tensor> {
603 let data = x.to_vec()?;
604 let activated: Vec<f32> = data.iter().map(|&v| v.tanh()).collect();
605 Ok(from_vec(
606 activated,
607 x.shape().dims(),
608 torsh_core::device::DeviceType::Cpu,
609 )?)
610 }
611
612 fn expand_to_nodes(&self, h: &Tensor, num_nodes: usize) -> Result<Tensor> {
613 let h_data = h.to_vec()?;
614 let feat_dim = h_data.len();
615
616 let mut expanded_data = Vec::new();
617 for _ in 0..num_nodes {
618 expanded_data.extend(&h_data);
619 }
620
621 Ok(from_vec(
622 expanded_data,
623 &[num_nodes, feat_dim],
624 torsh_core::device::DeviceType::Cpu,
625 )?)
626 }
627
628 fn generate_edges(&self, _h: &Tensor, num_nodes: usize) -> Result<Tensor> {
629 let mut edges = Vec::new();
630 let mut rng = thread_rng();
631
632 for i in 0..num_nodes {
634 for j in (i + 1)..num_nodes {
635 if rng.gen_range(0.0..1.0) > 0.7 {
637 edges.push(i as f32);
638 edges.push(j as f32);
639 edges.push(j as f32);
640 edges.push(i as f32);
641 }
642 }
643 }
644
645 if edges.is_empty() {
646 return Ok(zeros(&[2, 0])?);
647 }
648
649 let num_edges = edges.len() / 2;
650 Ok(from_vec(
651 edges,
652 &[2, num_edges],
653 torsh_core::device::DeviceType::Cpu,
654 )?)
655 }
656
657 fn parameters(&self) -> Vec<Tensor> {
658 let mut params = vec![
659 self.layer1.clone_data(),
660 self.layer2.clone_data(),
661 self.node_layer.clone_data(),
662 self.edge_layer.clone_data(),
663 ];
664
665 if let Some(ref b) = self.bias1 {
666 params.push(b.clone_data());
667 }
668 if let Some(ref b) = self.bias2 {
669 params.push(b.clone_data());
670 }
671
672 params
673 }
674}
675
676#[derive(Debug)]
678struct GraphGANDiscriminator {
679 input_features: usize,
680 hidden_dim: usize,
681
682 layer1: Parameter,
683 layer2: Parameter,
684 output_layer: Parameter,
685
686 bias1: Option<Parameter>,
687 bias2: Option<Parameter>,
688 bias_out: Option<Parameter>,
689}
690
691impl GraphGANDiscriminator {
692 fn new(input_features: usize, hidden_dim: usize, use_bias: bool) -> Result<Self> {
693 let layer1 = Parameter::new(randn(&[input_features, hidden_dim])?);
694 let layer2 = Parameter::new(randn(&[hidden_dim, hidden_dim])?);
695 let output_layer = Parameter::new(randn(&[hidden_dim, 1])?);
696
697 let (bias1, bias2, bias_out) = if use_bias {
698 (
699 Some(Parameter::new(zeros(&[hidden_dim])?)),
700 Some(Parameter::new(zeros(&[hidden_dim])?)),
701 Some(Parameter::new(zeros(&[1])?)),
702 )
703 } else {
704 (None, None, None)
705 };
706
707 Ok(Self {
708 input_features,
709 hidden_dim,
710 layer1,
711 layer2,
712 output_layer,
713 bias1,
714 bias2,
715 bias_out,
716 })
717 }
718
719 fn forward_logit(&self, graph: &GraphData) -> Result<f32> {
721 let mut h = graph.x.matmul(&self.layer1.clone_data())?;
723 if let Some(ref bias) = self.bias1 {
724 h = h.add(&bias.clone_data())?;
725 }
726 h = self.leaky_relu(&h, 0.2)?;
727
728 h = h.matmul(&self.layer2.clone_data())?;
729 if let Some(ref bias) = self.bias2 {
730 h = h.add(&bias.clone_data())?;
731 }
732 h = self.leaky_relu(&h, 0.2)?;
733
734 let graph_repr = h.mean(Some(&[0]), false)?;
736 let graph_repr_2d = graph_repr.unsqueeze(0)?; let mut logit = graph_repr_2d.matmul(&self.output_layer.clone_data())?;
740 if let Some(ref bias) = self.bias_out {
741 logit = logit.add(&bias.clone_data())?;
742 }
743
744 logit.item()
745 }
746
747 fn forward(&self, graph: &GraphData) -> Result<f32> {
749 let logit_val = self.forward_logit(graph)?;
750 Ok(1.0 / (1.0 + (-logit_val).exp()))
751 }
752
753 fn leaky_relu(&self, x: &Tensor, alpha: f32) -> Result<Tensor> {
754 let data = x.to_vec()?;
755 let activated: Vec<f32> = data
756 .iter()
757 .map(|&v| if v > 0.0 { v } else { alpha * v })
758 .collect();
759 Ok(from_vec(
760 activated,
761 x.shape().dims(),
762 torsh_core::device::DeviceType::Cpu,
763 )?)
764 }
765
766 fn parameters(&self) -> Vec<Tensor> {
767 let mut params = vec![
768 self.layer1.clone_data(),
769 self.layer2.clone_data(),
770 self.output_layer.clone_data(),
771 ];
772
773 if let Some(ref b) = self.bias1 {
774 params.push(b.clone_data());
775 }
776 if let Some(ref b) = self.bias2 {
777 params.push(b.clone_data());
778 }
779 if let Some(ref b) = self.bias_out {
780 params.push(b.clone_data());
781 }
782
783 params
784 }
785}
786
787#[derive(Debug)]
789pub struct ConditionalGraphGenerator {
790 vae: GraphVAE,
791 condition_dim: usize,
792 condition_layer: Parameter,
793}
794
795impl ConditionalGraphGenerator {
796 pub fn new(
798 in_features: usize,
799 hidden_features: usize,
800 latent_dim: usize,
801 condition_dim: usize,
802 beta: f32,
803 ) -> Result<Self> {
804 let vae = GraphVAE::new(in_features, hidden_features, latent_dim, beta, true)?;
805 let condition_layer = Parameter::new(randn(&[condition_dim, latent_dim])?);
806
807 Ok(Self {
808 vae,
809 condition_dim,
810 condition_layer,
811 })
812 }
813
814 pub fn generate_conditional(&self, condition: &Tensor, num_nodes: usize) -> Result<GraphData> {
816 let condition_bias = condition.matmul(&self.condition_layer.clone_data())?;
818
819 let z_base = randn(&[1, self.vae.latent_dim])?;
821
822 let z = z_base.add(&condition_bias)?;
824
825 self.vae.decode(&z, num_nodes)
827 }
828
829 fn parameters(&self) -> Vec<Tensor> {
830 let mut params = self.vae.parameters();
831 params.push(self.condition_layer.clone_data());
832 params
833 }
834}
835
836#[cfg(test)]
837mod tests {
838 use super::*;
839 use torsh_core::device::DeviceType;
840
841 #[test]
842 fn test_graphvae_creation() {
843 let vae = GraphVAE::new(8, 16, 10, 1.0, true).expect("operation should succeed");
844 assert_eq!(vae.encoder_in_features, 8);
845 assert_eq!(vae.encoder_hidden_features, 16);
846 assert_eq!(vae.latent_dim, 10);
847 assert_eq!(vae.beta, 1.0);
848 }
849
850 #[test]
851 fn test_graphvae_encode_decode() {
852 let features = randn(&[5, 8]).unwrap();
853 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0, 3.0, 4.0];
854 let edge_index = from_vec(edges, &[2, 4], DeviceType::Cpu).unwrap();
855 let graph = GraphData::new(features, edge_index);
856
857 let vae = GraphVAE::new(8, 16, 10, 1.0, true).expect("operation should succeed");
858
859 let (mu, logvar) = vae.encode(&graph).expect("operation should succeed");
860 assert_eq!(mu.shape().dims(), &[1, 10]);
861 assert_eq!(logvar.shape().dims(), &[1, 10]);
862
863 let z = vae
864 .reparameterize(&mu, &logvar)
865 .expect("operation should succeed");
866 assert_eq!(z.shape().dims(), &[1, 10]);
867
868 let reconstructed = vae.decode(&z, 5).expect("operation should succeed");
869 assert_eq!(reconstructed.num_nodes, 5);
870 }
871
872 #[test]
873 fn test_graphvae_generation() {
874 let vae = GraphVAE::new(8, 16, 10, 1.0, true).expect("operation should succeed");
875 let generated = vae.generate(6).expect("operation should succeed");
876
877 assert_eq!(generated.num_nodes, 6);
878 assert_eq!(generated.x.shape().dims()[0], 6);
879 assert_eq!(generated.x.shape().dims()[1], 8);
880 }
881
882 #[test]
883 fn test_graphvae_interpolation() {
884 let features1 = randn(&[4, 6]).unwrap();
885 let features2 = randn(&[4, 6]).unwrap();
886 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0];
887 let edge_index = from_vec(edges.clone(), &[2, 3], DeviceType::Cpu).unwrap();
888
889 let graph1 = GraphData::new(features1, edge_index.clone());
890 let graph2 = GraphData::new(features2, edge_index);
891
892 let vae = GraphVAE::new(6, 12, 8, 1.0, true).expect("operation should succeed");
893
894 let interpolated = vae
896 .interpolate(&graph1, &graph2, 0.5, 4)
897 .expect("operation should succeed");
898 assert_eq!(interpolated.num_nodes, 4);
899 }
900
901 #[test]
902 fn test_graphgan_creation() {
903 let gan = GraphGAN::new(16, 32, 8, true).expect("operation should succeed");
904 assert_eq!(gan.latent_dim, 16);
905 assert_eq!(gan.hidden_dim, 32);
906 assert_eq!(gan.output_features, 8);
907 }
908
909 #[test]
910 fn test_graphgan_generation() {
911 let gan = GraphGAN::new(16, 32, 8, true).expect("operation should succeed");
912 let generated = gan.generate(5).expect("operation should succeed");
913
914 assert_eq!(generated.num_nodes, 5);
915 assert_eq!(generated.x.shape().dims()[1], 8);
916 }
917
918 #[test]
919 fn test_graphgan_discriminate() {
920 let features = randn(&[4, 8]).unwrap();
921 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0];
922 let edge_index = from_vec(edges, &[2, 3], DeviceType::Cpu).unwrap();
923 let graph = GraphData::new(features, edge_index);
924
925 let gan = GraphGAN::new(16, 32, 8, true).expect("operation should succeed");
926 let score = gan.discriminate(&graph).expect("operation should succeed");
927
928 assert!(score >= 0.0 && score <= 1.0);
929 }
930
931 #[test]
932 fn test_conditional_generation() {
933 let cond_gen =
934 ConditionalGraphGenerator::new(8, 16, 10, 4, 1.0).expect("operation should succeed");
935
936 let condition = randn(&[1, 4]).unwrap();
937 let generated = cond_gen
938 .generate_conditional(&condition, 5)
939 .expect("operation should succeed");
940
941 assert_eq!(generated.num_nodes, 5);
942 assert_eq!(generated.x.shape().dims()[1], 8);
943 }
944
945 #[test]
946 fn test_graphvae_loss_computation() {
947 let features = randn(&[3, 6]).unwrap();
948 let edges = vec![0.0, 1.0, 1.0, 2.0];
949 let edge_index = from_vec(edges, &[2, 2], DeviceType::Cpu).unwrap();
950 let graph = GraphData::new(features, edge_index);
951
952 let vae = GraphVAE::new(6, 12, 8, 1.0, true).expect("operation should succeed");
953 let (reconstructed, mu, logvar) = vae.forward(&graph).expect("operation should succeed");
954
955 let loss = vae
956 .compute_loss(&graph, &reconstructed, &mu, &logvar)
957 .expect("operation should succeed");
958 assert!(loss > 0.0);
959 }
960
961 fn deterministic_unit_interval(name: &str, index: u64) -> f32 {
967 let mut hash: u64 = 0xcbf2_9ce4_8422_2325; for byte in name.bytes() {
969 hash ^= u64::from(byte);
970 hash = hash.wrapping_mul(0x0000_0100_0000_01b3); }
972 hash ^= index.wrapping_add(0x9E37_79B9_7F4A_7C15);
973 hash = (hash ^ (hash >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
974 hash = (hash ^ (hash >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
975 hash ^= hash >> 31;
976 ((hash >> 40) as f32) / (1u64 << 24) as f32
977 }
978
979 fn reinit_params(params: &[Tensor], label: &str) {
998 for (i, tensor) in params.iter().enumerate() {
999 let dims = tensor.shape().dims().to_vec();
1000 if dims.len() != 2 {
1001 continue;
1002 }
1003 let numel: usize = dims.iter().product();
1004 let bound = (6.0 / (dims[0] + dims[1]) as f32).sqrt();
1005 let name = format!("{label}.param{i}");
1006 let values: Vec<f32> = (0..numel)
1007 .map(|j| bound * (2.0 * deterministic_unit_interval(&name, j as u64) - 1.0))
1008 .collect();
1009 tensor
1010 .set_slice(0, &values)
1011 .expect("deterministic reinit set_slice should succeed");
1012 }
1013 }
1014
1015 fn deterministic_reinit_gan(gan: &GraphGAN) {
1042 reinit_params(&gan.generator_parameters(), "gan.generator");
1043 reinit_params(&gan.discriminator_parameters(), "gan.discriminator");
1044 }
1045
1046 #[test]
1047 fn test_graphgan_losses() {
1048 let features = randn(&[4, 8]).unwrap();
1049 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0];
1050 let edge_index = from_vec(edges, &[2, 3], DeviceType::Cpu).unwrap();
1051 let graph = GraphData::new(features, edge_index);
1052
1053 let gan = GraphGAN::new(16, 32, 8, true).expect("operation should succeed");
1054 deterministic_reinit_gan(&gan);
1058
1059 let gen_loss = gan.generator_loss(4).expect("operation should succeed");
1060 assert!(gen_loss > 0.0);
1061
1062 let disc_loss = gan
1063 .discriminator_loss(&graph, 4)
1064 .expect("operation should succeed");
1065 assert!(disc_loss.is_finite());
1067 }
1068
1069 #[test]
1084 fn discriminator_logit_stays_bounded_across_many_random_graphs() {
1085 let gan = GraphGAN::new(16, 32, 8, true).expect("operation should succeed");
1086 deterministic_reinit_gan(&gan);
1087
1088 for _ in 0..2000 {
1089 let features = randn(&[4, 8]).expect("randn");
1090 let edges = vec![0.0, 1.0, 1.0, 2.0, 2.0, 3.0];
1091 let edge_index = from_vec(edges, &[2, 3], DeviceType::Cpu).expect("from_vec");
1092 let graph = GraphData::new(features, edge_index);
1093
1094 let real_logit = gan.discriminate_logit(&graph).expect("discriminate_logit");
1095 assert!(
1096 real_logit.abs() < 50.0,
1097 "discriminator logit strayed far enough from 0 to approach the \
1098 softplus underflow edge: {real_logit}"
1099 );
1100
1101 let gen_loss = gan.generator_loss(4).expect("generator_loss");
1102 assert!(gen_loss > 0.0, "gen_loss was not > 0.0: {gen_loss}");
1103
1104 let disc_loss = gan
1105 .discriminator_loss(&graph, 4)
1106 .expect("discriminator_loss");
1107 assert!(
1108 disc_loss.is_finite(),
1109 "disc_loss was non-finite: {disc_loss}"
1110 );
1111 }
1112 }
1113}