use std::sync::Arc;
use std::sync::Mutex;
use crate::tree::HadamardProductNode;
use crate::tree::TraceNode;
use super::fmt::PrintTree;
use super::leaf::LeafNode;
use super::matmul::MatMulNode;
use super::outer::OuterProductNode;
use super::transpose::TransposeNode;
use qudit_core::ParamInfo;
use qudit_expr::ExpressionCache;
use qudit_expr::ExpressionId;
use qudit_expr::GenerationShape;
use qudit_expr::index::IndexDirection;
use qudit_expr::index::IndexId;
use qudit_expr::index::TensorIndex;
#[derive(PartialEq, Eq, Clone)]
pub enum TTGTNode {
MatMul(MatMulNode),
Outer(OuterProductNode),
Hadamard(HadamardProductNode),
Transpose(TransposeNode),
Trace(TraceNode),
Leaf(LeafNode),
}
#[derive(Clone)]
pub struct TTGTTree {
pub root: TTGTNode,
pub expressions: Arc<Mutex<ExpressionCache>>,
}
impl TTGTTree {
pub fn indices(&self) -> Vec<TensorIndex> {
self.root.indices()
}
pub fn generation_shape(&self) -> GenerationShape {
self.indices().into()
}
pub fn rank(&self) -> usize {
self.indices().len()
}
pub fn contract(
self,
right: Self,
shared_ids: Vec<IndexId>,
contraction_ids: Vec<IndexId>,
) -> Self {
if !Arc::ptr_eq(&self.expressions, &right.expressions) {
panic!("Contracting two TTGT trees with different expression caches.");
}
let left = self;
let left_left_indices = left
.indices()
.iter()
.filter(|idx| !shared_ids.contains(&idx.index_id()))
.filter(|idx| !contraction_ids.contains(&idx.index_id()))
.copied()
.collect::<Vec<TensorIndex>>();
let left_index_transpose = shared_ids
.iter()
.copied()
.chain(left_left_indices.iter().map(|idx| idx.index_id()))
.chain(contraction_ids.iter().copied())
.map(|i| {
left.indices()
.iter()
.position(|x| x.index_id() == i)
.unwrap()
})
.collect::<Vec<usize>>();
let left_index_redirection = shared_ids
.iter()
.map(|_| IndexDirection::Batch)
.chain(left_left_indices.iter().map(|_| IndexDirection::Output))
.chain(contraction_ids.iter().map(|_| IndexDirection::Input))
.collect();
let left_transposed_tree = left.transpose(left_index_transpose, left_index_redirection);
let right_right_indices = right
.indices()
.iter()
.filter(|idx| !shared_ids.contains(&idx.index_id()))
.filter(|idx| !contraction_ids.contains(&idx.index_id()))
.copied()
.collect::<Vec<TensorIndex>>();
let right_index_transpose = shared_ids
.iter()
.copied()
.chain(contraction_ids.iter().copied())
.chain(right_right_indices.iter().map(|idx| idx.index_id()))
.map(|i| {
right
.indices()
.iter()
.position(|x| x.index_id() == i)
.unwrap()
})
.collect::<Vec<usize>>();
let right_index_redirection = shared_ids
.iter()
.map(|_| IndexDirection::Batch)
.chain(contraction_ids.iter().map(|_| IndexDirection::Output))
.chain(right_right_indices.iter().map(|_| IndexDirection::Input))
.collect();
let right_transposed_tree = right.transpose(right_index_transpose, right_index_redirection);
if contraction_ids.is_empty() {
if left_transposed_tree.rank() == shared_ids.len()
&& right_transposed_tree.rank() == left_transposed_tree.rank()
{
left_transposed_tree.hadamard(right_transposed_tree)
} else {
left_transposed_tree.outer(right_transposed_tree)
}
} else {
left_transposed_tree.matmul(right_transposed_tree)
}
}
pub fn outer(self, right: Self) -> Self {
if !Arc::ptr_eq(&self.expressions, &right.expressions) {
panic!("Contracting two TTGT trees with different expression caches.");
}
Self {
root: TTGTNode::Outer(OuterProductNode::new(self.root, right.root)),
expressions: self.expressions,
}
}
pub fn hadamard(self, right: Self) -> Self {
if !Arc::ptr_eq(&self.expressions, &right.expressions) {
panic!("Contracting two TTGT trees with different expression caches.");
}
Self {
root: TTGTNode::Hadamard(HadamardProductNode::new(self.root, right.root)),
expressions: self.expressions,
}
}
pub fn matmul(self, right: Self) -> Self {
if !Arc::ptr_eq(&self.expressions, &right.expressions) {
panic!("Contracting two TTGT trees with different expression caches.");
}
Self {
root: TTGTNode::MatMul(MatMulNode::new(self.root, right.root)),
expressions: self.expressions,
}
}
pub fn transpose(self, perm: Vec<usize>, redirection: Vec<IndexDirection>) -> Self {
let is_identity_permutation = perm.iter().enumerate().all(|(i, &p)| i == p);
let original_directions: Vec<IndexDirection> =
self.indices().iter().map(|idx| idx.direction()).collect();
let is_identity_redirection = redirection == original_directions;
let new_root = if is_identity_permutation && is_identity_redirection {
self.root
} else if let TTGTNode::Transpose(n) = self.root {
let TransposeNode {
child,
perm: base_perm,
..
} = n;
let composed_perm: Vec<usize> = base_perm.iter().map(|&idx| perm[idx]).collect();
TTGTNode::Transpose(TransposeNode::new(*child, composed_perm, redirection))
} else if let TTGTNode::Leaf(n) = self.root {
let expr_perm = n.convert_tensor_perm_to_expression_perm(&perm);
let LeafNode {
expr: expr_id,
param_info,
indices,
tensor_to_expr_position_map,
} = n;
let new_indices = perm
.iter()
.map(|p| indices[*p])
.zip(redirection.iter())
.map(|(idx, new_direction)| {
TensorIndex::new(*new_direction, idx.index_id(), idx.index_size())
})
.collect::<Vec<TensorIndex>>();
let new_shape: GenerationShape = (&new_indices).into();
let new_expr_id = self
.expressions
.lock()
.unwrap()
.permute_reshape(expr_id, expr_perm, new_shape);
TTGTNode::Leaf(LeafNode::new(
new_expr_id,
param_info,
new_indices,
tensor_to_expr_position_map,
))
} else {
TTGTNode::Transpose(TransposeNode::new(self.root, perm, redirection))
};
Self {
root: new_root,
expressions: self.expressions,
}
}
pub fn leaf(
expressions: Arc<Mutex<ExpressionCache>>,
expr: ExpressionId,
param_info: ParamInfo,
indices: Vec<TensorIndex>,
tensor_to_expr_position_map: Vec<Vec<usize>>,
) -> Self {
Self {
root: TTGTNode::Leaf(LeafNode::new(
expr,
param_info,
indices,
tensor_to_expr_position_map,
)),
expressions,
}
}
pub fn trace(self, pairs: Vec<(usize, usize)>) -> Self {
let new_root = if pairs.is_empty() {
self.root
} else {
TTGTNode::Trace(TraceNode::new(self.root, pairs))
};
Self {
root: new_root,
expressions: self.expressions,
}
}
}
impl TTGTNode {
pub fn indices(&self) -> Vec<TensorIndex> {
match self {
Self::MatMul(s) => s.indices(),
Self::Outer(s) => s.indices(),
Self::Hadamard(s) => s.indices(),
Self::Transpose(s) => s.indices(),
Self::Trace(s) => s.indices(),
Self::Leaf(s) => s.indices(),
}
}
pub fn param_info(&self) -> ParamInfo {
match self {
Self::MatMul(s) => s.param_info(),
Self::Outer(s) => s.param_info(),
Self::Hadamard(s) => s.param_info(),
Self::Transpose(s) => s.param_info(),
Self::Trace(s) => s.param_info(),
Self::Leaf(s) => s.param_info(),
}
}
}
impl std::hash::Hash for TTGTNode {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
Self::MatMul(s) => s.hash(state),
Self::Outer(s) => s.hash(state),
Self::Hadamard(s) => s.hash(state),
Self::Transpose(s) => s.hash(state),
Self::Trace(s) => s.hash(state),
Self::Leaf(s) => s.hash(state),
}
}
}
impl PrintTree for TTGTTree {
fn write_tree(&self, prefix: &str, fmt: &mut std::fmt::Formatter<'_>) {
match &self.root {
TTGTNode::MatMul(s) => s.write_tree(prefix, fmt),
TTGTNode::Outer(s) => s.write_tree(prefix, fmt),
TTGTNode::Hadamard(s) => s.write_tree(prefix, fmt),
TTGTNode::Transpose(s) => s.write_tree(prefix, fmt),
TTGTNode::Trace(s) => s.write_tree(prefix, fmt),
TTGTNode::Leaf(s) => s.write_tree(prefix, fmt),
}
}
}
impl std::fmt::Debug for TTGTTree {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.write_tree("", f); Ok(())
}
}
impl PrintTree for TTGTNode {
fn write_tree(&self, prefix: &str, fmt: &mut std::fmt::Formatter<'_>) {
match self {
Self::MatMul(s) => s.write_tree(prefix, fmt),
Self::Outer(s) => s.write_tree(prefix, fmt),
Self::Hadamard(s) => s.write_tree(prefix, fmt),
Self::Transpose(s) => s.write_tree(prefix, fmt),
Self::Trace(s) => s.write_tree(prefix, fmt),
Self::Leaf(s) => s.write_tree(prefix, fmt),
}
}
}
impl std::fmt::Debug for TTGTNode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.write_tree("", f); Ok(())
}
}