1#![allow(dead_code)]
7type Result<T, E = torsh_core::error::TorshError> = std::result::Result<T, E>;
10
11use crate::parameter::Parameter;
12use std::collections::HashMap;
13use torsh_tensor::{
14 creation::{randn, zeros},
15 Tensor,
16};
17
18pub type NodeType = String;
20
21pub type EdgeType = (NodeType, String, NodeType);
23
24#[derive(Debug, Clone)]
26pub struct HeteroGraphData {
27 pub node_features: HashMap<NodeType, Tensor>,
29 pub edge_indices: HashMap<EdgeType, Tensor>,
31 pub edge_attributes: HashMap<EdgeType, Option<Tensor>>,
33 pub num_nodes: HashMap<NodeType, usize>,
35}
36
37impl HeteroGraphData {
38 pub fn new() -> Self {
40 Self {
41 node_features: HashMap::new(),
42 edge_indices: HashMap::new(),
43 edge_attributes: HashMap::new(),
44 num_nodes: HashMap::new(),
45 }
46 }
47
48 pub fn add_node_type(&mut self, node_type: NodeType, features: Tensor) -> &mut Self {
50 let num_nodes = features.shape().dims()[0];
51 self.node_features.insert(node_type.clone(), features);
52 self.num_nodes.insert(node_type, num_nodes);
53 self
54 }
55
56 pub fn add_edge_type(
58 &mut self,
59 edge_type: EdgeType,
60 edge_index: Tensor,
61 edge_attr: Option<Tensor>,
62 ) -> &mut Self {
63 self.edge_indices.insert(edge_type.clone(), edge_index);
64 self.edge_attributes.insert(edge_type, edge_attr);
65 self
66 }
67
68 pub fn node_types(&self) -> Vec<&NodeType> {
70 self.node_features.keys().collect()
71 }
72
73 pub fn edge_types(&self) -> Vec<&EdgeType> {
75 self.edge_indices.keys().collect()
76 }
77}
78
79#[derive(Debug)]
81pub struct HeteroGNN {
82 node_types: Vec<NodeType>,
83 edge_types: Vec<EdgeType>,
84 node_transformations: HashMap<NodeType, Parameter>,
86 edge_transformations: HashMap<EdgeType, Parameter>,
88 out_features: usize,
90 bias: bool,
92 biases: HashMap<NodeType, Option<Parameter>>,
94}
95
96impl HeteroGNN {
97 pub fn new(
99 node_type_dims: HashMap<NodeType, usize>,
100 edge_types: Vec<EdgeType>,
101 out_features: usize,
102 bias: bool,
103 ) -> Result<Self> {
104 let mut node_transformations = HashMap::new();
105 let mut biases = HashMap::new();
106
107 for (node_type, in_features) in &node_type_dims {
109 let weight = Parameter::new(randn(&[*in_features, out_features])?);
110 node_transformations.insert(node_type.clone(), weight);
111
112 let bias_param = if bias {
113 Some(Parameter::new(zeros(&[out_features])?))
114 } else {
115 None
116 };
117 biases.insert(node_type.clone(), bias_param);
118 }
119
120 let mut edge_transformations = HashMap::new();
122 for edge_type in &edge_types {
123 let weight = Parameter::new(randn(&[out_features, out_features])?);
125 edge_transformations.insert(edge_type.clone(), weight);
126 }
127
128 Ok(Self {
129 node_types: node_type_dims.keys().cloned().collect(),
130 edge_types,
131 node_transformations,
132 edge_transformations,
133 out_features,
134 bias,
135 biases,
136 })
137 }
138
139 pub fn forward(&self, hetero_graph: &HeteroGraphData) -> Result<HeteroGraphData> {
141 let mut output_features = HashMap::new();
142
143 let mut transformed_features = HashMap::new();
145 for node_type in &self.node_types {
146 if let Some(features) = hetero_graph.node_features.get(node_type) {
147 if let Some(transform) = self.node_transformations.get(node_type) {
148 let mut transformed = features.matmul(&transform.clone_data())?;
149
150 if let Some(Some(bias)) = self.biases.get(node_type) {
152 transformed = transformed.add(&bias.clone_data())?;
153 }
154
155 transformed_features.insert(node_type.clone(), transformed);
156 }
157 }
158 }
159
160 let mut aggregated_messages = HashMap::new();
162
163 for edge_type in &self.edge_types {
164 let (src_type, relation, dst_type) = edge_type;
165
166 if let (Some(edge_index), Some(src_features), Some(edge_transform)) = (
167 hetero_graph.edge_indices.get(edge_type),
168 transformed_features.get(src_type),
169 self.edge_transformations.get(edge_type),
170 ) {
171 let edge_flat = edge_index.to_vec()?;
173 let num_edges = edge_flat.len() / 2;
174
175 if num_edges > 0 {
176 let src_indices = &edge_flat[0..num_edges];
177 let dst_indices = &edge_flat[num_edges..];
178
179 let dst_num_nodes = hetero_graph.num_nodes.get(dst_type).unwrap_or(&0);
181 let messages = zeros(&[*dst_num_nodes, self.out_features])?;
182
183 for edge_idx in 0..num_edges {
185 let src_node = src_indices[edge_idx] as usize;
186 let dst_node = dst_indices[edge_idx] as usize;
187
188 let src_feat = src_features
190 .slice_tensor(0, src_node, src_node + 1)?
191 .squeeze_tensor(0)?;
192
193 let message = src_feat
195 .unsqueeze_tensor(0)?
196 .matmul(&edge_transform.clone_data())?
197 .squeeze_tensor(0)?;
198
199 let mut dst_slice = messages.slice_tensor(0, dst_node, dst_node + 1)?;
201 let current_msg = dst_slice.squeeze_tensor(0)?;
202 let updated_msg = current_msg.add(&message)?;
203 let _ = dst_slice.copy_(&updated_msg.unsqueeze_tensor(0)?);
204 }
205
206 aggregated_messages.insert(
208 (src_type.clone(), relation.clone(), dst_type.clone()),
209 messages,
210 );
211 }
212 }
213 }
214
215 for node_type in &self.node_types {
217 let mut node_output = if let Some(self_features) = transformed_features.get(node_type) {
218 self_features.clone()
219 } else {
220 continue;
221 };
222
223 for edge_type in &self.edge_types {
225 let (_, _, dst_type) = edge_type;
226 if dst_type == node_type {
227 if let Some(messages) = aggregated_messages.get(edge_type) {
228 node_output = node_output.add(messages)?;
229 }
230 }
231 }
232
233 let zero_tensor = zeros(node_output.shape().dims())?;
235 node_output = node_output.maximum(&zero_tensor)?;
236
237 output_features.insert(node_type.clone(), node_output);
238 }
239
240 let mut output = HeteroGraphData::new();
242 output.node_features = output_features;
243 output.edge_indices = hetero_graph.edge_indices.clone();
244 output.edge_attributes = hetero_graph.edge_attributes.clone();
245 output.num_nodes = hetero_graph.num_nodes.clone();
246
247 Ok(output)
248 }
249
250 pub fn parameters(&self) -> Vec<Tensor> {
252 let mut params = Vec::new();
253
254 for transform in self.node_transformations.values() {
256 params.push(transform.clone_data());
257 }
258
259 for transform in self.edge_transformations.values() {
261 params.push(transform.clone_data());
262 }
263
264 for bias_opt in self.biases.values() {
266 if let Some(bias) = bias_opt {
267 params.push(bias.clone_data());
268 }
269 }
270
271 params
272 }
273}
274
275#[derive(Debug)]
277pub struct HeteroGAT {
278 node_types: Vec<NodeType>,
279 edge_types: Vec<EdgeType>,
280 query_transforms: HashMap<NodeType, Parameter>,
282 key_transforms: HashMap<NodeType, Parameter>,
283 value_transforms: HashMap<NodeType, Parameter>,
284 relation_attentions: HashMap<EdgeType, Parameter>,
286 heads: usize,
288 out_features: usize,
290 dropout: f32,
292}
293
294impl HeteroGAT {
295 pub fn new(
297 node_type_dims: HashMap<NodeType, usize>,
298 edge_types: Vec<EdgeType>,
299 out_features: usize,
300 heads: usize,
301 dropout: f32,
302 ) -> Result<Self> {
303 let mut query_transforms = HashMap::new();
304 let mut key_transforms = HashMap::new();
305 let mut value_transforms = HashMap::new();
306
307 for (node_type, in_features) in &node_type_dims {
309 let q = Parameter::new(randn(&[*in_features, heads * out_features])?);
310 let k = Parameter::new(randn(&[*in_features, heads * out_features])?);
311 let v = Parameter::new(randn(&[*in_features, heads * out_features])?);
312
313 query_transforms.insert(node_type.clone(), q);
314 key_transforms.insert(node_type.clone(), k);
315 value_transforms.insert(node_type.clone(), v);
316 }
317
318 let mut relation_attentions = HashMap::new();
320 for edge_type in &edge_types {
321 let attention = Parameter::new(randn(&[heads, 2 * out_features])?);
322 relation_attentions.insert(edge_type.clone(), attention);
323 }
324
325 Ok(Self {
326 node_types: node_type_dims.keys().cloned().collect(),
327 edge_types,
328 query_transforms,
329 key_transforms,
330 value_transforms,
331 relation_attentions,
332 heads,
333 out_features,
334 dropout,
335 })
336 }
337
338 pub fn forward(&self, hetero_graph: &HeteroGraphData) -> Result<HeteroGraphData> {
340 let mut output_features = HashMap::new();
341
342 let mut queries = HashMap::new();
344 let mut keys = HashMap::new();
345 let mut values = HashMap::new();
346
347 for node_type in &self.node_types {
348 if let Some(features) = hetero_graph.node_features.get(node_type) {
349 let q = features.matmul(&self.query_transforms[node_type].clone_data())?;
350 let k = features.matmul(&self.key_transforms[node_type].clone_data())?;
351 let v = features.matmul(&self.value_transforms[node_type].clone_data())?;
352
353 let num_nodes = features.shape().dims()[0];
355 let q_reshaped = q.view(&[
356 num_nodes as i32,
357 self.heads as i32,
358 self.out_features as i32,
359 ])?;
360 let k_reshaped = k.view(&[
361 num_nodes as i32,
362 self.heads as i32,
363 self.out_features as i32,
364 ])?;
365 let v_reshaped = v.view(&[
366 num_nodes as i32,
367 self.heads as i32,
368 self.out_features as i32,
369 ])?;
370
371 queries.insert(node_type.clone(), q_reshaped);
372 keys.insert(node_type.clone(), k_reshaped);
373 values.insert(node_type.clone(), v_reshaped);
374 }
375 }
376
377 for dst_type in &self.node_types {
379 let dst_num_nodes = hetero_graph.num_nodes.get(dst_type).unwrap_or(&0);
380 let aggregated_output = zeros(&[*dst_num_nodes, self.heads * self.out_features])?;
381
382 for edge_type in &self.edge_types {
384 let (src_type, _relation, target_type) = edge_type;
385
386 if target_type != dst_type {
387 continue;
388 }
389
390 if let (
391 Some(edge_index),
392 Some(_src_queries),
393 Some(_dst_keys),
394 Some(src_values),
395 Some(_attention_params),
396 ) = (
397 hetero_graph.edge_indices.get(edge_type),
398 queries.get(src_type),
399 keys.get(dst_type),
400 values.get(src_type),
401 self.relation_attentions.get(edge_type),
402 ) {
403 let edge_flat = edge_index.to_vec()?;
407 let num_edges = edge_flat.len() / 2;
408
409 if num_edges > 0 {
410 let src_indices = &edge_flat[0..num_edges];
411 let dst_indices = &edge_flat[num_edges..];
412
413 for edge_idx in 0..num_edges {
415 let src_node = src_indices[edge_idx] as usize;
416 let dst_node = dst_indices[edge_idx] as usize;
417
418 let src_value = src_values
420 .slice_tensor(0, src_node, src_node + 1)?
421 .view(&[1, (self.heads * self.out_features) as i32])?
422 .squeeze_tensor(0)?;
423
424 let mut dst_slice =
426 aggregated_output.slice_tensor(0, dst_node, dst_node + 1)?;
427 let current = dst_slice.squeeze_tensor(0)?;
428 let updated = current.add(&src_value)?;
429 let _ = dst_slice.copy_(&updated.unsqueeze_tensor(0)?);
430 }
431 }
432 }
433 }
434
435 output_features.insert(dst_type.clone(), aggregated_output);
436 }
437
438 let mut output = HeteroGraphData::new();
440 output.node_features = output_features;
441 output.edge_indices = hetero_graph.edge_indices.clone();
442 output.edge_attributes = hetero_graph.edge_attributes.clone();
443 output.num_nodes = hetero_graph.num_nodes.clone();
444
445 Ok(output)
446 }
447
448 pub fn parameters(&self) -> Vec<Tensor> {
450 let mut params = Vec::new();
451
452 for transform in self.query_transforms.values() {
454 params.push(transform.clone_data());
455 }
456 for transform in self.key_transforms.values() {
457 params.push(transform.clone_data());
458 }
459 for transform in self.value_transforms.values() {
460 params.push(transform.clone_data());
461 }
462
463 for attention in self.relation_attentions.values() {
465 params.push(attention.clone_data());
466 }
467
468 params
469 }
470}
471
472#[derive(Debug)]
474pub struct KnowledgeGraphEmbedding {
475 entity_types: Vec<NodeType>,
476 relation_types: Vec<String>,
477 entity_embeddings: HashMap<NodeType, Parameter>,
479 relation_embeddings: HashMap<String, Parameter>,
481 embedding_dim: usize,
483}
484
485impl KnowledgeGraphEmbedding {
486 pub fn new(
488 entity_types: Vec<NodeType>,
489 relation_types: Vec<String>,
490 num_entities: HashMap<NodeType, usize>,
491 embedding_dim: usize,
492 ) -> Result<Self> {
493 let mut entity_embeddings = HashMap::new();
494 let mut relation_embeddings = HashMap::new();
495
496 for entity_type in &entity_types {
498 let num = num_entities.get(entity_type).unwrap_or(&100);
499 let embeddings = Parameter::new(randn(&[*num, embedding_dim])?);
500 entity_embeddings.insert(entity_type.clone(), embeddings);
501 }
502
503 for relation in &relation_types {
505 let embeddings = Parameter::new(randn(&[embedding_dim, embedding_dim])?);
506 relation_embeddings.insert(relation.clone(), embeddings);
507 }
508
509 Ok(Self {
510 entity_types,
511 relation_types,
512 entity_embeddings,
513 relation_embeddings,
514 embedding_dim,
515 })
516 }
517
518 pub fn get_entity_embedding(
520 &self,
521 entity_type: &NodeType,
522 entity_id: usize,
523 ) -> Result<Option<Tensor>> {
524 if let Some(embeddings) = self.entity_embeddings.get(entity_type) {
525 Ok(Some(
526 embeddings
527 .clone_data()
528 .slice_tensor(0, entity_id, entity_id + 1)?
529 .squeeze_tensor(0)?,
530 ))
531 } else {
532 Ok(None)
533 }
534 }
535
536 pub fn triple_score(
538 &self,
539 head_type: &NodeType,
540 head_id: usize,
541 relation: &String,
542 tail_type: &NodeType,
543 tail_id: usize,
544 ) -> Result<Option<f64>> {
545 if let (Some(head_emb), Some(tail_emb), Some(rel_emb)) = (
546 self.get_entity_embedding(head_type, head_id)?,
547 self.get_entity_embedding(tail_type, tail_id)?,
548 self.relation_embeddings.get(relation),
549 ) {
550 let head_plus_rel = head_emb
552 .unsqueeze_tensor(0)?
553 .matmul(&rel_emb.clone_data())?
554 .squeeze_tensor(0)?;
555
556 let diff = head_plus_rel.sub(&tail_emb)?;
557 let score_tensor = diff.dot(&diff)?;
558 let score = score_tensor.to_vec()?[0] as f64;
559
560 Ok(Some(-score)) } else {
562 Ok(None)
563 }
564 }
565
566 pub fn parameters(&self) -> Vec<Tensor> {
568 let mut params = Vec::new();
569
570 for emb in self.entity_embeddings.values() {
571 params.push(emb.clone_data());
572 }
573
574 for emb in self.relation_embeddings.values() {
575 params.push(emb.clone_data());
576 }
577
578 params
579 }
580}
581
582#[cfg(test)]
583mod tests {
584 use super::*;
585 use torsh_core::device::DeviceType;
586 use torsh_tensor::creation::from_vec;
587
588 #[test]
589 fn test_hetero_graph_creation() {
590 let mut hetero_graph = HeteroGraphData::new();
591
592 let user_features = from_vec(vec![1.0, 2.0, 3.0, 4.0], &[2, 2], DeviceType::Cpu)
594 .expect("from vec should succeed");
595 hetero_graph.add_node_type("user".to_string(), user_features);
596
597 let item_features = from_vec(
599 vec![5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
600 &[2, 3],
601 DeviceType::Cpu,
602 )
603 .expect("operation should succeed");
604 hetero_graph.add_node_type("item".to_string(), item_features);
605
606 let edge_index = from_vec(vec![0.0, 1.0, 0.0, 1.0], &[2, 2], DeviceType::Cpu)
608 .expect("from vec should succeed");
609 hetero_graph.add_edge_type(
610 ("user".to_string(), "likes".to_string(), "item".to_string()),
611 edge_index,
612 None,
613 );
614
615 assert_eq!(hetero_graph.node_types().len(), 2);
616 assert_eq!(hetero_graph.edge_types().len(), 1);
617 }
618
619 #[test]
620 fn test_hetero_gnn_creation() {
621 let mut node_dims = HashMap::new();
622 node_dims.insert("user".to_string(), 2);
623 node_dims.insert("item".to_string(), 3);
624
625 let edge_types = vec![("user".to_string(), "likes".to_string(), "item".to_string())];
626
627 let hetero_gnn = HeteroGNN::new(node_dims, edge_types, 8, true);
628 let params = hetero_gnn.expect("operation should succeed").parameters();
629
630 assert!(params.len() >= 4);
632 }
633
634 #[test]
635 fn test_knowledge_graph_embeddings() {
636 let entity_types = vec!["person".to_string(), "company".to_string()];
637 let relation_types = vec!["works_at".to_string(), "founded".to_string()];
638
639 let mut num_entities = HashMap::new();
640 num_entities.insert("person".to_string(), 10);
641 num_entities.insert("company".to_string(), 5);
642
643 let kg_emb = KnowledgeGraphEmbedding::new(entity_types, relation_types, num_entities, 50)
644 .expect("operation should succeed");
645
646 let person_emb = kg_emb
648 .get_entity_embedding(&"person".to_string(), 0)
649 .expect("operation should succeed");
650 assert!(person_emb.is_some());
651
652 let emb = person_emb;
653 assert_eq!(emb.expect("operation should succeed").shape().dims(), &[50]);
654
655 let score = kg_emb
657 .triple_score(
658 &"person".to_string(),
659 0,
660 &"works_at".to_string(),
661 &"company".to_string(),
662 0,
663 )
664 .expect("operation should succeed");
665 assert!(score.is_some());
666 assert!(score.expect("operation should succeed").is_finite());
667 }
668}