use super::{
check_ok_needed_outputs, gather_operands, next_node_specific_function, ParseNode,
ParseNodeFunctional,
};
use crate::ast_node::{ASTContext, Node, TokenSlice};
use crate::operations::Operation;
use crate::syntax_error::SyntaxError;
use crate::tokens::Token;
use crate::types::{FunctionSignature, IJType};
use anyhow::Result;
use std::rc::Rc;
pub struct GeneralizedContraction;
impl GeneralizedContraction {
fn first_signature() -> FunctionSignature {
FunctionSignature::new(vec![IJType::Tensor(None)], IJType::Number(None))
}
fn second_signature() -> FunctionSignature {
FunctionSignature::number_function(2)
}
fn get_functional_part(
slice: TokenSlice,
context: &mut ASTContext,
) -> Result<(Rc<Node>, Rc<Node>, TokenSlice)> {
let (f_node, rest) = next_node_specific_function(slice, context, Self::first_signature())?;
let (g_node, rest) = next_node_specific_function(rest, context, Self::second_signature())?;
Ok((f_node, g_node, rest))
}
fn get_number_type_input_output(
f_node: &Rc<Node>,
g_node: &Rc<Node>,
) -> Result<(Option<String>, Option<String>)> {
let f_number_type_output = f_node
.output_type
.extract_signature()
.unwrap()
.output
.extract_number_type()
.unwrap_or_default();
let f_number_type_input = f_node.output_type.extract_signature().unwrap().input[0]
.extract_number_type()
.unwrap_or_default();
let g_number_type = g_node
.output_type
.extract_signature()
.unwrap()
.output
.extract_number_type()
.unwrap_or_default();
if let (Some(n), Some(m)) = (f_number_type_input.clone(), g_number_type.clone()) {
if n != m {
return Err(SyntaxError::NumberTypeMismatch(n, m).into());
}
}
match (g_number_type, f_number_type_input, f_number_type_output) {
(Some(n1), _, Some(n2)) => Ok((Some(n1.clone()), Some(n2.clone()))),
(Some(n1), _, None) => Ok((Some(n1.clone()), Some(n1.clone()))),
(None, Some(n1), Some(n2)) => Ok((Some(n1.clone()), Some(n2.clone()))),
(None, None, Some(n2)) => Ok((None, Some(n2.clone()))),
_ => Ok((None, None)),
}
}
}
impl ParseNode for GeneralizedContraction {
fn next_node(
_op: Token,
slice: TokenSlice,
context: &mut ASTContext,
) -> Result<(Rc<Node>, TokenSlice)> {
let (f_node, g_node, rest) = Self::get_functional_part(slice, context)?;
let (number_type_input, number_type_output) =
Self::get_number_type_input_output(&f_node, &g_node)?;
let (operands, rest) = gather_operands(
vec![vec![
IJType::Tensor(number_type_input.clone()),
IJType::Tensor(number_type_input.clone()),
]],
rest,
context,
)?;
let output_type = IJType::Tensor(number_type_output.clone());
let node = Node::new(
Operation::GeneralizedContraction,
vec![
f_node.output_type.clone(),
g_node.output_type.clone(),
operands[0].output_type.clone(),
operands[1].output_type.clone(),
],
output_type,
vec![f_node, g_node, operands[0].clone(), operands[1].clone()],
context,
)?;
Ok((Rc::new(node), rest))
}
}
impl ParseNodeFunctional for GeneralizedContraction {
fn next_node_functional_impl(
_op: Token,
slice: TokenSlice,
context: &mut ASTContext,
needed_outputs: Option<&[IJType]>,
) -> Result<(Vec<Rc<Node>>, TokenSlice)> {
let slice = slice.move_start(1)?;
if !check_ok_needed_outputs(needed_outputs, &IJType::Tensor(None)) {
return Err(SyntaxError::RequiredOutputsDoNotMatchFunctionOutputs(
format!("{:?}", needed_outputs),
IJType::Tensor(None).to_string(),
)
.into());
}
let (f_node, g_node, rest) = Self::get_functional_part(slice, context)?;
let (number_type_input, number_type_output) =
Self::get_number_type_input_output(&f_node, &g_node)?;
let node = Node::new(
Operation::GeneralizedContraction,
vec![f_node.output_type.clone(), g_node.output_type.clone()],
IJType::Function(FunctionSignature::new(
vec![
IJType::Tensor(number_type_input.clone()),
IJType::Tensor(number_type_input),
],
IJType::Tensor(number_type_output),
)),
vec![f_node, g_node],
context,
)?;
Ok((vec![Rc::new(node)], rest))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::{parse_str, parse_str_no_context};
#[test]
fn test_generalized_contraction() -> Result<()> {
let (node, _) = parse_str_no_context("?/+* [1] [2]")?;
assert_eq!(node.op, Operation::GeneralizedContraction);
Ok(())
}
#[test]
fn test_generalized_contraction_functional() -> Result<()> {
let (node, _) = parse_str_no_context("~?/+*")?;
assert_eq!(node.op, Operation::GeneralizedContraction);
Ok(())
}
#[test]
fn test_generalized_contraction_functional_apply() -> Result<()> {
let (node, _) = parse_str_no_context(".~?/+* [1] [2]")?;
assert_eq!(node.op, Operation::Apply);
let node = node.operands[0].clone();
assert_eq!(node.op, Operation::GeneralizedContraction);
Ok(())
}
#[test]
fn test_generalized_contraction_number_type() -> Result<()> {
let (node, _) = parse_str_no_context("?/+* [1]<a> [2]")?;
assert_eq!(node.op, Operation::GeneralizedContraction);
assert_eq!(node.output_type, IJType::Tensor(Some("a".to_string())));
let (node, _) = parse_str_no_context("?/+* [1] [2]<a>")?;
assert_eq!(node.op, Operation::GeneralizedContraction);
assert_eq!(node.output_type, IJType::Tensor(Some("a".to_string())));
let (node, _) = parse_str_no_context("?/+* [1]<a> [2]<a>")?;
assert_eq!(node.op, Operation::GeneralizedContraction);
assert_eq!(node.output_type, IJType::Tensor(Some("a".to_string())));
let result = parse_str_no_context("?/+* [1]<a> [2]<b>");
assert!(result.is_err());
Ok(())
}
#[test]
fn test_generalized_contraction_functions() -> Result<()> {
let mut context = ASTContext::new();
parse_str("var f: Fn(T->N)", &mut context)?;
parse_str("var g: Fn(N,N->N)", &mut context)?;
let node = parse_str("~? f g", &mut context)?;
assert_eq!(node.op, Operation::GeneralizedContraction);
assert_eq!(
node.output_type,
IJType::Function(FunctionSignature::new(
vec![IJType::Tensor(None), IJType::Tensor(None)],
IJType::Tensor(None)
))
);
Ok(())
}
#[test]
fn test_generalized_contraction_functions_number_type() -> Result<()> {
let mut context = ASTContext::new();
parse_str("var f: Fn(T->N)", &mut context)?;
parse_str("var g: Fn(N<f64>,N<f64>->N<f64>)", &mut context)?;
let node = parse_str("? f g [1] [2]", &mut context)?;
assert_eq!(node.op, Operation::GeneralizedContraction);
assert_eq!(node.output_type, IJType::Tensor(Some("f64".to_string())));
let mut context = ASTContext::new();
parse_str("var f: Fn(T<a>->N<b>)", &mut context)?;
parse_str("var g: Fn(N<a>,N<a>->N<a>)", &mut context)?;
let node = parse_str("? f g [1]<a> [2]<a>", &mut context)?;
assert_eq!(node.op, Operation::GeneralizedContraction);
assert_eq!(node.output_type, IJType::Tensor(Some("b".to_string())));
let mut context = ASTContext::new();
parse_str("var f: Fn(T<a>->N<b>)", &mut context)?;
parse_str("var g: Fn(N,N->N)", &mut context)?;
let node = parse_str("? f g [1]<a> [2]<a>", &mut context)?;
assert_eq!(node.op, Operation::GeneralizedContraction);
assert_eq!(node.output_type, IJType::Tensor(Some("b".to_string())));
Ok(())
}
#[test]
fn test_generalized_contraction_functional_number_type() -> Result<()> {
let mut context = ASTContext::new();
parse_str("var f: Fn(T->N)", &mut context)?;
parse_str("var g: Fn(N<f64>,N<f64>->N<f64>)", &mut context)?;
let node = parse_str("~? f g", &mut context)?;
assert_eq!(node.op, Operation::GeneralizedContraction);
assert_eq!(
node.output_type,
IJType::Function(FunctionSignature::new(
vec![
IJType::Tensor(Some("f64".to_string())),
IJType::Tensor(Some("f64".to_string()))
],
IJType::Tensor(Some("f64".to_string()))
))
);
let mut context = ASTContext::new();
parse_str("var f: Fn(T<f64>->N<f32>)", &mut context)?;
parse_str("var g: Fn(N<f64>,N<f64>->N<f64>)", &mut context)?;
let node = parse_str("~? f g", &mut context)?;
assert_eq!(node.op, Operation::GeneralizedContraction);
assert_eq!(
node.output_type,
IJType::Function(FunctionSignature::new(
vec![
IJType::Tensor(Some("f64".to_string())),
IJType::Tensor(Some("f64".to_string()))
],
IJType::Tensor(Some("f32".to_string()))
))
);
let mut context = ASTContext::new();
parse_str("var f: Fn(T<a>->N<b>)", &mut context)?;
parse_str("var g: Fn(N,N->N)", &mut context)?;
let node = parse_str("~? f g", &mut context)?;
assert_eq!(node.op, Operation::GeneralizedContraction);
assert_eq!(
node.output_type,
IJType::Function(FunctionSignature::new(
vec![
IJType::Tensor(Some("a".to_string())),
IJType::Tensor(Some("a".to_string()))
],
IJType::Tensor(Some("b".to_string()))
))
);
Ok(())
}
}