From 97ee1727a442740cd4c05e9861ac0bf931695dd0 Mon Sep 17 00:00:00 2001 From: lcnbr Date: Mon, 3 Jun 2024 12:03:25 +0200 Subject: [PATCH] docs: :memo: added presentation for FORM and Symbolica dev conf --- examples/gamma_chain.rs | 4 +- examples/gamma_network.rs | 2 +- examples/pres.rs | 138 + flake.nix | 2 + pres/diag.svg | 345 +++ pres/luLogo.png | Bin 0 -> 135138 bytes pres/pres.html | 5779 +++++++++++++++++++++++++++++++++++++ pres/pres.qmd | 738 +++++ pres/spenso.png | Bin 0 -> 10932 bytes src/structure.rs | 2 + src/tests.rs | 70 +- 11 files changed, 7069 insertions(+), 11 deletions(-) create mode 100644 examples/pres.rs create mode 100644 pres/diag.svg create mode 100644 pres/luLogo.png create mode 100644 pres/pres.html create mode 100644 pres/pres.qmd create mode 100644 pres/spenso.png diff --git a/examples/gamma_chain.rs b/examples/gamma_chain.rs index 072047f..177b8d0 100644 --- a/examples/gamma_chain.rs +++ b/examples/gamma_chain.rs @@ -227,7 +227,7 @@ fn main() { one.mul_fallible(4.3).unwrap(), ]; - let spacings: [i32; 2] = [20, 24]; + let spacings: [i32; 2] = [2, 4]; let mut start = 1; let mut ranges = Vec::new(); @@ -268,7 +268,7 @@ fn main() { #[cfg(feature = "shadowing")] { - let mut chain = gamma_net(&vec, vbar, u); + let mut chain = gamma_net_param(&vec); println!("{}", chain.graph.edges.len()); println!("{}", chain.graph.nodes.len()); println!("{}", chain.graph.involution.len()); diff --git a/examples/gamma_network.rs b/examples/gamma_network.rs index 55722f6..3cd2432 100644 --- a/examples/gamma_network.rs +++ b/examples/gamma_network.rs @@ -59,7 +59,7 @@ fn main() { one.mul_fallible(4.3).unwrap(), ]; - let spacings: [i32; 2] = [20, 24]; + let spacings: [i32; 2] = [2, 4]; let mut start = 1; let mut ranges = Vec::new(); diff --git a/examples/pres.rs b/examples/pres.rs new file mode 100644 index 0000000..c26f751 --- /dev/null +++ b/examples/pres.rs @@ -0,0 +1,138 @@ +use std::ops::Neg; + +use spenso::{ + ufo::{ + euclidean_four_vector, euclidean_four_vector_sym, gamma, gammasym, mink_four_vector, + mink_four_vector_sym, param_euclidean_four_vector, param_mink_four_vector, + }, + AbstractIndex, Complex, Contract, Dimension, FallibleMul, HasName, HasStructure, + HistoryStructure, MixedTensor, NamedStructure, NumTensor, Representation, Shadowable, Slot, + SparseTensor, SymbolicTensor, TensorNetwork, +}; + +use criterion::{criterion_group, criterion_main, Criterion}; +use num::ToPrimitive; + +use symbolica::atom::{Atom, Symbol}; + +fn gamma_net_sym( + minkindices: &[i32], + vbar: [Complex; 4], + u: [Complex; 4], +) -> TensorNetwork>> { + let mut i = 0; + let mut contracting_index = 0.into(); + let mut result: Vec>> = + vec![euclidean_four_vector_sym(contracting_index, &vbar).into()]; + for m in minkindices { + let ui = contracting_index; + contracting_index += 1.into(); + let uj = contracting_index; + if *m > 0 { + let p = [ + Complex::::new(1.0 + 0.01 * i.to_f64().unwrap(), 0.0), + Complex::::new(1.1 + 0.01 * i.to_f64().unwrap(), 0.0), + Complex::::new(1.2 + 0.01 * i.to_f64().unwrap(), 0.0), + Complex::::new(1.3 + 0.01 * i.to_f64().unwrap(), 0.0), + ]; + i += 1; + result.push(mink_four_vector_sym(usize::try_from(*m).unwrap().into(), &p).into()); + result.push(gammasym(usize::try_from(*m).unwrap().into(), (ui, uj)).into()); + } else { + result.push( + gammasym( + AbstractIndex::from(usize::try_from(m.neg()).unwrap() + 10000), + (ui, uj), + ) + .into(), + ); + } + } + result.push(euclidean_four_vector_sym(contracting_index, &u).into()); + TensorNetwork::from(result) +} + +fn indices(n: i32, m: i32) -> Vec { + let spacings: [i32; 2] = [n, m]; + let mut start = 1; + let mut ranges = Vec::new(); + + for &spacing in spacings.iter() { + ranges.push((start..start + spacing).chain(std::iter::once(-1))); + start += spacing; + } + + ranges.into_iter().flatten().collect() +} + +fn main() { + let one = Complex::::new(1.0, 0.0); + let _zero = Complex::::new(0.0, 0.0); + + let vbar = [ + one.mul_fallible(3.0).unwrap(), + one.mul_fallible(3.1).unwrap(), + one.mul_fallible(3.2).unwrap(), + one.mul_fallible(3.3).unwrap(), + ]; + let u = [ + one.mul_fallible(4.0).unwrap(), + one.mul_fallible(4.1).unwrap(), + one.mul_fallible(4.2).unwrap(), + one.mul_fallible(4.3).unwrap(), + ]; + let minkindices = indices(20, 24); + + let mut netsym = gamma_net_sym(&minkindices, vbar, u); + + println!("one:{}", netsym.dot()); + netsym.contract_algo(|s| TensorNetwork::edge_to_min_degree_node_with_depth(&s, 5)); + println!("two:{}", netsym.dot()); + netsym.contract_algo(|s| TensorNetwork::edge_to_min_degree_node_with_depth(&s, 10)); + println!("three:{}", netsym.dot()); + + let mink = Representation::Lorentz(Dimension(4)); + let mu = Slot::from((AbstractIndex(0), mink)); + let spin = Representation::SpinFundamental(Dimension(4)); + let spina = Representation::SpinAntiFundamental(Dimension(4)); + + let i = Slot::from((AbstractIndex(1), spin)); + let j = Slot::from((AbstractIndex(2), spina)); + let k = Slot::from((9.into(), spin)); + + let structure = NamedStructure::from_slots(vec![mu, i, j], "γ"); + let p_struct = NamedStructure::from_slots(vec![mu], "p"); + let t_struct = NamedStructure::from_slots(vec![i, j, k], "T"); + + let gamma_sym = SymbolicTensor::from_named(&structure).unwrap(); + let p_sym = SymbolicTensor::from_named(&p_struct).unwrap(); + let t_sym = SymbolicTensor::from_named(&t_struct).unwrap(); + + let f = gamma_sym + .contract(&p_sym) + .unwrap() + .contract(&t_sym) + .unwrap(); + + println!("{}", *f.get_atom()); + + let a: TensorNetwork = f.to_network().unwrap(); + + // println!("{}", a.dot()); + + // let γ1: MixedTensor<_> = gamma(1.into(), (1.into(), 2.into())).into(); + + // let γ2: MixedTensor<_> = gamma(10.into(), (2.into(), 3.into())).into(); + + let p1: MixedTensor<_> = param_mink_four_vector(1.into(), "p1").into(); + + let u: MixedTensor<_> = NamedStructure::new( + &[(1.into(), Representation::SpinFundamental(4.into()))], + "u", + ) + .shadow() + .unwrap() + .into(); + + println!("{}", u.to_symbolic().unwrap()); +} diff --git a/flake.nix b/flake.nix index df753d4..c9b32d6 100644 --- a/flake.nix +++ b/flake.nix @@ -174,6 +174,8 @@ pkgs.cargo-insta pkgs.git # pkgs.ripgrep + pkgs.quarto + pkgs.deno ]; }; }); diff --git a/pres/diag.svg b/pres/diag.svg new file mode 100644 index 0000000..6798467 --- /dev/null +++ b/pres/diag.svg @@ -0,0 +1,345 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pres/luLogo.png b/pres/luLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..00d22cf7159c24011c11bd813a4948e2d84fd633 GIT binary patch literal 135138 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%0AR)K@oIwaxUNmR*}BgQh_8Xq2UX`vlVvOEVvDA4 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�}U0i(!>T8<_^$mp$IPu-5bx;w5}~ucNb7EDe_E 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 literal 0 HcmV?d00001 diff --git a/pres/pres.html b/pres/pres.html new file mode 100644 index 0000000..28ce20d --- /dev/null +++ b/pres/pres.html @@ -0,0 +1,5779 @@ + + + + + + + + + + + + + + Spenso + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pres/pres.qmd b/pres/pres.qmd new file mode 100644 index 0000000..3e235b6 --- /dev/null +++ b/pres/pres.qmd @@ -0,0 +1,738 @@ +--- +title: "Spenso" +subtitle: "a rust-based framework for symbolic and numerical tensor computation" +author: "Lucien Huber" +institute: "Bern University" +format: + revealjs: + slide-number: true + chalkboard: false + auto-animate: true + incremental: true + transition: slide + transition-speed: fast + preview-links: auto + logo: luLogo.png + code-overflow: wrap + highlight-style: breeze + embed-resources: true + footnotes-hover: true +--- + +# Introduction + +Tensors are pervasive in High Energy Physics. + + +We wanted a library that implements tensor contractions, and arithmetic, component wise and synergises well with symbolica. + + +::: {.notes} + For our project with Mathjis, Zeno, and Valentin we wanted a library that implements tensor contractions, and arithmetic, all component wise. + + I will go more in depth, into our use case but tensors have many uses. +::: + +## Uses + +Evaluating: + +- Feynman Rules (e.g. gamma chains) +- IBP (multi-dim. array) +- Tensor Networks for lattice QCD + +::: {.notes} + Component wise tensors are good for evaluating applied feynman rules of course. (lorentz and spinor structure) +::: + +## It already exists, no? + +Not really + +- Mathematica + - Too slow +- NDarray (Rust) + - Wanted Einstein/Abstract Index notation + - Needed Generic Dimensions +- cuTensorNet (CUDA) + - Support for non-euclidean metric + - Physical indices +- ITensors.jl (Julia/C++) + - Wanted Rust (for LU/gammaloop), zero-cost abstractions FTW + - Does not talk Symbolica + + +::: {.notes} + Why make such a library when this isn't a new concept? + + Well no package really fits our bill.. + + Mathematica: we need to evaluate tensor contractions at each integration point, it needs to be fast + + We use rust: for nice language features and speed. + + Rust: tensor package ecosystem is barren + + if we wanted speed: + + cutensoret + + there does exist a very close in feature set library +::: + + + +# Introducing Spenso! + +![](spenso.png){fig-align="center" width=100} + +- Provides two data storage layouts: + - Dense (flat vector) + - Sparse (hashmap based) + +- The components can be any type + - Crucially Symbolica `Atom` +- Can create tensor "networks" for deferred contraction + + +## Initializing + +Each tensor needs at minimum ae index `Structure`. +The `Structure` encodes the indices of the tensor, including the representation and dimensionality + +```rust +let mink = Representation::Lorentz(Dimension(4)); +let mu = Slot::from((AbstractIndex(0),mink)); +let bis = Representation::Bispinor(Dimension(4)); +let i = Slot::from((AbstractIndex(1),bis)); +let j = Slot::from((AbstractIndex(2),bis)); + +let structure = NamedStructure::new(&[mu,i,j],"γ"); +``` +Represents: +$$ +\gamma^{\mu}_{ij} +$$ + + + +## Adding data + +Now we can add data to this structure. +```{.rust code-line-numbers="|2|4|5"} +let iunit = Complex::::i(); +let mut gamma = SparseTensor::empty(structure); + +gamma.set(&[0, 0, 0], 1.); +gamma.set(&[1, 1, 0], 1.); +gamma.set(&[2, 2, 0], -1.); +gamma.set(&[3, 3, 0], -1.); + +gamma.set(&[0, 3, 1], 1.); +gamma.set(&[1, 2, 1], 1.); +gamma.set(&[2, 1, 1], -1.); +gamma.set(&[3, 0, 1], -1.); + +gamma.set(&[0, 3, 2], -iunit); +gamma.set(&[1, 2, 2], iunit); +gamma.set(&[2, 1, 2], iunit); +gamma.set(&[3, 0, 2], -iunit); + +gamma.set(&[0, 2, 3], 1.); +gamma.set(&[1, 3, 3], -1.); +gamma.set(&[2, 0, 3], -1.); +gamma.set(&[3, 1, 3], 1.); +``` + + +:::{.notes} + you can do this from a flattened vector, directly creating a dense tensor. You can also easily convert between the two storage options. +::: + +## What if I don't yet know the data: + +The tensor can be symbolic, or parametric! + +```{.rust code-line-numbers="|4|5|7|"} +let nu = Slot::from((AbstractIndex(1), mink)); +let structure = NamedStructure::from_slots(vec![mu, nu], "T"); + +let symbolicT: SymbolicTensor = SymbolicTensor::from_named(&structure).unwrap(); +println!("{}", symbolicT); // T(lor(4,0),lor(4,1)) +let paramT: DenseTensor = symbolicT.shadow().unwrap(); +let t12: Atom = paramT.get(&[1, 2]).unwrap());// T(1,2) +``` + +## What can you do with tensors: + +- Iterate along specific dimensions: + ```rust + for (c, _, _) in gamma.fiber(&[true, false, true].into()).iter() { + //stuff + } + ``` +- Arithmetic, when structures match: + ```rust + let 2gamma = gamma+gamma + ``` +- Contractions, when indices match: + ```rust + let cont = gamma.contract(¶mT); + ``` + +## Specific example {auto-animate=true } + +consider this triangle diagram: + +![](diag.svg){fig-align="center" width="30%"} + +The numerator spinor structure looks like + + + +$$p_1^{\mu_1} p_2^{\mu_2}p_3^{\mu_3} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_2} \gamma_\rho \gamma_{\mu_3})$$ + + +:::{.notes} +what do we use it for? +::: + +## Specific example {auto-animate=true } + + +$$p_1^{\mu_1} p_2^{\mu_2}p_3^{\mu_3} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_2} \gamma_\rho \gamma_{\mu_3})$$ + +Usually we turn this into dot products by repeatedly applying the defining relation: + +$$\left\{ \gamma_\mu, \gamma_\nu \right\} = \gamma_\mu \gamma_\nu + \gamma_\nu \gamma_\mu = 2 \eta_{\mu \nu} I_4$$ + + +## Specific example {auto-animate=true } + + +$$2 p_1^{\mu_1} p_2^{\mu_2}p_{3,\rho} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_2}) \\ +-p_1^{\mu_1} p_2^{\mu_2}p_3^{\mu_3} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_2} \gamma_{\mu_3}\gamma_\rho)$$ + +Usually we turn this into dot products by repeatedly applying the defining relation: + +$$\left\{ \gamma_\mu, \gamma_\nu \right\} = \gamma_\mu \gamma_\nu + \gamma_\nu \gamma_\mu = 2 \eta_{\mu \nu} I_4$$ + +## Specific example {auto-animate=true } + + +$$2 p_1^{\mu_1} p_2^{\mu_2}p_{3,\rho} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_2}) \\ +-2 p_2 \cdot p_3 p_1^{\mu_1} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_\rho) \\ ++p_1^{\mu_1} p_2^{\mu_2}p_3^{\mu_3} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_3}\gamma_{\mu_2} \gamma_\rho) +$$ + + +Usually we turn this into dot products by repeatedly applying the defining relation: + +$$\left\{ \gamma_\mu, \gamma_\nu \right\} = \gamma_\mu \gamma_\nu + \gamma_\nu \gamma_\mu = 2 \eta_{\mu \nu} I_4$$ + + +:::{.notes} +gamma mu 3 is parading down the length of the gamma chain,leaving a trail of shorter chains, and dot products +::: + +## Specific example {auto-animate=true } + + +$$-4(p_1 \cdot p_2) p_{3,\mu}\eta_{\nu\rho} + \\ +4(p_1\cdot p_2)p_{3,\nu}\eta_{\mu\rho} + \\ -4(p_1\cdot p_2)p_{3,\rho}\eta_{\mu\nu} + \\ +4(p_1\cdot p_3)p_{2,\mu}\eta_{\nu\rho} + \\ -4(p_1\cdot p_3)p_{2,\nu}\eta_{\mu\rho} + \\ -4(p_1\cdot p_3)p_{2,\rho}\eta_{\mu\nu} + \\ -4p_{1,\mu} (p_2\cdot p_3)\eta_{\nu\rho} + \\ +4p_{1,\mu} p_{2,\nu}p_{3,\rho} + \\ +4p_{1,\mu} p_{2,\rho}p_{3,\nu} + \\ -4p_{1,\nu}(p_2\cdot p_3)\eta_{\mu\rho} + \\ +4p_{1,\nu}p_{2,\mu}p_{3,\rho} + \\ +4p_{1,\nu}p_{2,\rho}p_{3,\mu} + \\ +4p_{1,\rho}(p_2\cdot p_3)\eta_{\mu\nu} + \\ -4p_{1,\rho}p_{2,\mu}p_{3,\nu} + \\ +4p_{1,\rho}p_{2,\nu}p_{3,\mu} +$$ + +## Problem + +Exponential growth of the terms: + + +::: {.nonincremental} +- length 6: 15 +- length 10: ~1000 +::: + +If you do componentwise contraction on the unmodified trace: + +$$p_1^{\mu_1} p_2^{\mu_2}p_3^{\mu_3} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_2} \gamma_\rho \gamma_{\mu_3})$$ + +The scaling is linear in the chain length! + + +## Deffered contraction {auto-animate=true } + +Consider a string of tensors + +Example + +::: {data-id="gammachain"} + +$$\bar{v}_{i_1} u_{i_3}p_1^{\mu_1} p_2^{\mu_2}p_3^{\mu_3}p_4^{\mu_4}p_5^{\mu_5}p_6^{\mu_6}p_7^{\mu_7} \gamma_{i_1 i_2}^{\mu_1} +\\ \gamma_{i_2 i_3}^{\nu} +\gamma_{i_4 i_5}^{\nu} +\gamma_{i_5 i_6}^{\mu_2} +\gamma_{i_6 i_7}^{\mu_3} +\gamma_{i_7 i_8}^{\mu_4} +\gamma_{i_8 i_9}^{\mu_5} +\gamma_{i_9 i_4}^{\mu_6}$$ + +::: + + + + + +## Deffered contraction {auto-animate=true } + +We can model a string of tensors, with repeated indices as a graph, each edge is an index, each node a tensor. + +Example + +::: {data-id="gammachain"} + +```{dot .fragment} +//| echo: false +graph { node [shape=circle,height=0.1,label=""]; overlap="scale"; +layout=neato + 4294967299 -- 4294967297 [color=red] + 4294967299 -- 4294967298 + 4294967301 -- 4294967299 + 4294967301 -- 4294967300 + 4294967302 -- 4294967301 [color=red] + 4294967304 -- 4294967302 + 4294967304 -- 4294967303[color=red] + 4294967306 -- 4294967304 + 4294967306 -- 4294967305 [color=red] + 4294967308 -- 4294967306 + 4294967308 -- 4294967307 [color=red] + 4294967310 -- 4294967308 + 4294967310 -- 4294967309 [color=red] + 4294967311 -- 4294967310 + 4294967311 -- 4294967302 + 4294967312 -- 4294967311 [color=red]} +graph { node [shape=circle,height=0.1,label=""]; overlap="scale";} +``` +::: + +## Deffered contraction {auto-animate=true } + +We can model a string of tensors, with repeated indices as a graph, each edge is an index, each node a tensor. + +Example + +::: {data-id="gammachain"} + +```{.rust} +let γ1: MixedTensor<_> = gamma(1.into(), (1.into(), 2.into())).into(); +println!("{}", γ1.to_symbolic().unwrap()); +//γ(lor(4,1),spin(4,1),spina(4,2)) +let γ2: MixedTensor<_> = gamma(10.into(), (2.into(), 3.into())).into(); +// ... +let p1: MixedTensor<_> = param_mink_four_vector(1.into(), "p1").into(); +/// ... +let u: MixedTensor<_> = param_four_vector(1.into(), "u").into(); +println!("{}", u.to_symbolic().unwrap()); +// u(spin(4,1)) + +let net: TensorNetwork = TensorNetwork::from(vec![u,v,p1,p2, + p3,p4,p5,p6, + γ1,γ2,γ3,γ4, + γ5,γ6,γ7,γ8]); + +println!("{}",net.dot()); +``` +::: + +## Tensor Network + +::: {.columns} +::: {.column width="30%" } +```{dot .fragment} +//| echo: false +//| fig-width: 3 +graph { node [shape=circle,height=0.1,label=""]; overlap="scale"; +layout=neato + 4294967299 -- 4294967297 [color=red] + 4294967299 -- 4294967298 + 4294967301 -- 4294967299 + 4294967301 -- 4294967300 + 4294967302 -- 4294967301 [color=red] + 4294967304 -- 4294967302 + 4294967304 -- 4294967303[color=red] + 4294967306 -- 4294967304 + 4294967306 -- 4294967305 [color=red] + 4294967308 -- 4294967306 + 4294967308 -- 4294967307 [color=red] + 4294967310 -- 4294967308 + 4294967310 -- 4294967309 [color=red] + 4294967311 -- 4294967310 + 4294967311 -- 4294967302 + 4294967312 -- 4294967311 [color=red]} +graph { node [shape=circle,height=0.1,label=""]; overlap="scale";} +``` + +::: + +::: {.column width="70%"} + +- We now essentially have a tensor network + +- Contraction needs to happen according to some algorithm + + ```{.rust .smaller} + net.contract_algo(Self::edge_to_min_degree_node) + + ``` + +- This can be custom. + - Don't need to contract all the way down + + +::: +::: + + +## Shadowing {auto-animate=true auto-animate-id="shadowing" } + +::: {.nonincremental} +- If some tensors are fully parametric, there is a quick explosion of terms. +- We can contract until a certain limit, then "shadow" the obtained network with parametric tensors +::: + +:::::: {.columns} +::: {.column .fragment width="40%"} + +```{dot .fragment data-id="shadow"} +//| echo: false +//| fig-width: 3 +graph { node [shape=circle,height=0.1,label=""]; overlap="scale"; +layout=neato +edge [penwidth=10.0] +start=rsts + 4294967299 -- 4294967297 + 4294967299 -- 4294967298 + 4294967301 -- 4294967299 + 4294967301 -- 4294967300 + 4294967303 -- 4294967301 + 4294967303 -- 4294967302 + 4294967305 -- 4294967303 + 4294967305 -- 4294967304 + 4294967307 -- 4294967305 + 4294967307 -- 4294967306 + 4294967309 -- 4294967307 + 4294967309 -- 4294967308 + 4294967311 -- 4294967309 + 4294967311 -- 4294967310 + 4294967313 -- 4294967311 + 4294967313 -- 4294967312 + 4294967315 -- 4294967313 + 4294967315 -- 4294967314 + 4294967317 -- 4294967315 + 4294967317 -- 4294967316 + 4294967319 -- 4294967317 + 4294967319 -- 4294967318 + 4294967321 -- 4294967319 + 4294967321 -- 4294967320 + 4294967323 -- 4294967321 + 4294967323 -- 4294967322 + 4294967325 -- 4294967323 + 4294967325 -- 4294967324 + 4294967327 -- 4294967325 + 4294967327 -- 4294967326 + 4294967329 -- 4294967327 + 4294967329 -- 4294967328 + 4294967331 -- 4294967329 + 4294967331 -- 4294967330 + 4294967333 -- 4294967331 + 4294967333 -- 4294967332 + 4294967335 -- 4294967333 + 4294967335 -- 4294967334 + 4294967337 -- 4294967335 + 4294967337 -- 4294967336 + 4294967338 -- 4294967337 + 4294967340 -- 4294967338 + 4294967340 -- 4294967339 + 4294967342 -- 4294967340 + 4294967342 -- 4294967341 + 4294967344 -- 4294967342 + 4294967344 -- 4294967343 + 4294967346 -- 4294967344 + 4294967346 -- 4294967345 + 4294967348 -- 4294967346 + 4294967348 -- 4294967347 + 4294967350 -- 4294967348 + 4294967350 -- 4294967349 + 4294967352 -- 4294967350 + 4294967352 -- 4294967351 + 4294967354 -- 4294967352 + 4294967354 -- 4294967353 + 4294967356 -- 4294967354 + 4294967356 -- 4294967355 + 4294967358 -- 4294967356 + 4294967358 -- 4294967357 + 4294967360 -- 4294967358 + 4294967360 -- 4294967359 + 4294967362 -- 4294967360 + 4294967362 -- 4294967361 + 4294967364 -- 4294967362 + 4294967364 -- 4294967363 + 4294967366 -- 4294967364 + 4294967366 -- 4294967365 + 4294967368 -- 4294967366 + 4294967368 -- 4294967367 + 4294967370 -- 4294967368 + 4294967370 -- 4294967369 + 4294967372 -- 4294967370 + 4294967372 -- 4294967371 + 4294967374 -- 4294967372 + 4294967374 -- 4294967373 + 4294967376 -- 4294967374 + 4294967376 -- 4294967375 + 4294967378 -- 4294967376 + 4294967378 -- 4294967377 + 4294967380 -- 4294967378 + 4294967380 -- 4294967379 + 4294967382 -- 4294967380 + 4294967382 -- 4294967381 + 4294967384 -- 4294967382 + 4294967384 -- 4294967383 + 4294967386 -- 4294967384 + 4294967386 -- 4294967385 + 4294967387 -- 4294967386 + 4294967387 -- 4294967338 + 4294967388 -- 4294967387} +``` + +::: +::: {.column .fragment width="30%"} +```{dot .fragment data-id="shadow"} +//| echo: false +//| fig-width: 3 +graph { node [shape=circle,height=0.1,label=""]; overlap="scale"; +edge [penwidth=3.0] +start=trst +layout=neato + 30064771165 -- 12884901893 + 21474836491 -- 30064771165 + 21474836497 -- 21474836491 + 21474836503 -- 21474836497 + 21474836509 -- 21474836503 + 21474836515 -- 21474836509 + 12884901930 -- 21474836515 + 21474836526 -- 12884901930 + 21474836532 -- 21474836526 + 21474836538 -- 21474836532 + 21474836544 -- 21474836538 + 21474836550 -- 21474836544 + 21474836556 -- 21474836550 + 21474836562 -- 21474836556 + 12884901976 -- 21474836562 + 12884901978 -- 12884901976 + 12884901978 -- 12884901930} +``` +::: +::: {.column .fragment width="30%"} + +```{dot .fragment data-id="shadow"} +//| echo: false +//| fig-width: 3 + +graph { node [shape=circle,height=0.1,label=""]; overlap="scale"; +edge [penwidth=2.0] +start=st +layout=neato + 38654705757 -- 133143986267 + 30064771089 -- 38654705757 + 30064771101 -- 30064771089 + 21474836522 -- 30064771101 + 30064771124 -- 21474836522 + 30064771136 -- 30064771124 + 30064771148 -- 30064771136 + 12884901978 -- 30064771148 + 12884901978 -- 30064771101} +``` +::: +:::::: + +- A similar idea has already been done in cuTensorNet! + +## Synergy with symbolica + +Spenso understands a specific format of symbolica expressions and can recognise known tensors with numerical counterparts: + +```rust +let structure = NamedStructure::from_slots(vec![mu, i, j], "γ"); +let p_struct = NamedStructure::from_slots(vec![mu], "p"); +let t_struct = NamedStructure::from_slots(vec![i, j, k], "T"); + +let gamma_sym = SymbolicTensor::from_named(&structure).unwrap(); +let p_sym = SymbolicTensor::from_named(&p_struct).unwrap(); +let t_sym = SymbolicTensor::from_named(&t_struct).unwrap(); + +let f = gamma_sym.contract(&p_sym).unwrap().contract(&t_sym).unwrap(); + +println!("{}", *f.get_atom()); +``` +```python +p(lor(4,0))*γ(lor(4,0),bis(4,1),bis(4,2))*T(bis(4,1),bis(4,2),bis(4,3)) +``` + +```rust +let net: TensorNetwork = f.to_network().unwrap(); +``` + +```{dot} +graph { node [shape=circle,height=0.1,label=""]; overlap="scale"; +layout=neato + 4294967298 -- 4294967297 + 4294967299 -- 4294967298 + 4294967299 -- 4294967298ext4294967303 [shape=none, label=""]; + 4294967299 -- ext4294967303;} +``` + +## Performance + + +:::::: {.columns} +::: {.column } +```{dot .fragment data-id="shadow"} +//| echo: false +//| fig-width: 2 +graph { node [shape=circle,height=0.1,label=""]; overlap="scale"; +edge [penwidth=10.0] + +layout=neato + 4294967299 -- 4294967297 + 4294967299 -- 4294967298 + 4294967301 -- 4294967299 + 4294967301 -- 4294967300 + 4294967303 -- 4294967301 + 4294967303 -- 4294967302 + 4294967305 -- 4294967303 + 4294967305 -- 4294967304 + 4294967307 -- 4294967305 + 4294967307 -- 4294967306 + 4294967309 -- 4294967307 + 4294967309 -- 4294967308 + 4294967311 -- 4294967309 + 4294967311 -- 4294967310 + 4294967313 -- 4294967311 + 4294967313 -- 4294967312 + 4294967315 -- 4294967313 + 4294967315 -- 4294967314 + 4294967317 -- 4294967315 + 4294967317 -- 4294967316 + 4294967319 -- 4294967317 + 4294967319 -- 4294967318 + 4294967321 -- 4294967319 + 4294967321 -- 4294967320 + 4294967323 -- 4294967321 + 4294967323 -- 4294967322 + 4294967325 -- 4294967323 + 4294967325 -- 4294967324 + 4294967327 -- 4294967325 + 4294967327 -- 4294967326 + 4294967329 -- 4294967327 + 4294967329 -- 4294967328 + 4294967331 -- 4294967329 + 4294967331 -- 4294967330 + 4294967333 -- 4294967331 + 4294967333 -- 4294967332 + 4294967335 -- 4294967333 + 4294967335 -- 4294967334 + 4294967337 -- 4294967335 + 4294967337 -- 4294967336 + 4294967338 -- 4294967337 + 4294967340 -- 4294967338 + 4294967340 -- 4294967339 + 4294967342 -- 4294967340 + 4294967342 -- 4294967341 + 4294967344 -- 4294967342 + 4294967344 -- 4294967343 + 4294967346 -- 4294967344 + 4294967346 -- 4294967345 + 4294967348 -- 4294967346 + 4294967348 -- 4294967347 + 4294967350 -- 4294967348 + 4294967350 -- 4294967349 + 4294967352 -- 4294967350 + 4294967352 -- 4294967351 + 4294967354 -- 4294967352 + 4294967354 -- 4294967353 + 4294967356 -- 4294967354 + 4294967356 -- 4294967355 + 4294967358 -- 4294967356 + 4294967358 -- 4294967357 + 4294967360 -- 4294967358 + 4294967360 -- 4294967359 + 4294967362 -- 4294967360 + 4294967362 -- 4294967361 + 4294967364 -- 4294967362 + 4294967364 -- 4294967363 + 4294967366 -- 4294967364 + 4294967366 -- 4294967365 + 4294967368 -- 4294967366 + 4294967368 -- 4294967367 + 4294967370 -- 4294967368 + 4294967370 -- 4294967369 + 4294967372 -- 4294967370 + 4294967372 -- 4294967371 + 4294967374 -- 4294967372 + 4294967374 -- 4294967373 + 4294967376 -- 4294967374 + 4294967376 -- 4294967375 + 4294967378 -- 4294967376 + 4294967378 -- 4294967377 + 4294967380 -- 4294967378 + 4294967380 -- 4294967379 + 4294967382 -- 4294967380 + 4294967382 -- 4294967381 + 4294967384 -- 4294967382 + 4294967384 -- 4294967383 + 4294967386 -- 4294967384 + 4294967386 -- 4294967385 + 4294967387 -- 4294967386 + 4294967387 -- 4294967338 + 4294967388 -- 4294967387} + ``` +::: +::: {.column} + +| Spenso | Spenso Compiled | Hardcoded Fortran | +|---------|:-----:|:------:| +| 104 μs | 16 μs | 31μs | + +We can use a compiled version of the stacked shadowing using symbolica! + +::: +:::::: +# Outlook + +- More tooling to deal with specific tensor index symmetries +- Python bindings compatible with symbolica +- GPU support +- SIMD support + + +# Thanks + +If you want to check it out it is on crates.io: + + + +And on github: + + \ No newline at end of file diff --git a/pres/spenso.png b/pres/spenso.png new file mode 100644 index 0000000000000000000000000000000000000000..96d4e0b5d150d2676ef47feac642eb44e742c1a5 GIT binary patch literal 10932 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

Bvm0aw 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 eXIDM`SmgmcdQYvnCkk+hPV15G!*VsN(EkD::i(); + let unit = Complex::::from(1.); + let mut gamma = SparseTensor::empty(structure); + + gamma.set(&[0, 0, 0], unit).unwrap(); + gamma.set(&[1, 1, 0], unit).unwrap(); + gamma.set(&[2, 2, 0], -unit).unwrap(); + gamma.set(&[3, 3, 0], -unit).unwrap(); + + gamma.set(&[0, 3, 1], unit).unwrap(); + gamma.set(&[1, 2, 1], unit).unwrap(); + gamma.set(&[2, 1, 1], -unit).unwrap(); + gamma.set(&[3, 0, 1], -unit).unwrap(); + + gamma.set(&[0, 3, 2], -iunit).unwrap(); + gamma.set(&[1, 2, 2], iunit).unwrap(); + gamma.set(&[2, 1, 2], iunit).unwrap(); + gamma.set(&[3, 0, 2], -iunit).unwrap(); + + gamma.set(&[0, 2, 3], unit).unwrap(); + gamma.set(&[1, 3, 3], -unit).unwrap(); + gamma.set(&[2, 0, 3], -unit).unwrap(); + gamma.set(&[3, 1, 3], unit).unwrap(); + + let nu = Slot::from((AbstractIndex(1), mink)); + let structure = NamedStructure::from_slots(vec![mu, nu], "T"); + + let symbolicT: SymbolicTensor = SymbolicTensor::from_named(&structure).unwrap(); + println!("{}", symbolicT); // T(lor(4,0),lor(4,1)) + let paramT: DenseTensor = symbolicT.shadow().unwrap(); + + println!("{}", paramT.get(&[1, 2]).unwrap()); + + for (c, _, _) in gamma.fiber([true, false, true].as_slice().into()).iter() { + //stuff + } +}

+
+ +
+

Spenso

+

a rust-based framework for symbolic and numerical tensor computation

+ +
+
+
+Lucien Huber +
+

+ Bern University +

+
+
+ +
+
+
+

Introduction

+

Tensors are pervasive in High Energy Physics.

+

We wanted a library that implements tensor contractions, and arithmetic, component wise and synergises well with symbolica.

+ +
+
+

Uses

+

Evaluating:

+
    +
  • Feynman Rules (e.g. gamma chains)
  • +
  • IBP (multi-dim. array)
  • +
  • Tensor Networks for lattice QCD
  • +
+ +
+
+

It already exists, no?

+

Not really

+
    +
  • Mathematica +
      +
    • Too slow
    • +
  • +
  • NDarray (Rust) +
      +
    • Wanted Einstein/Abstract Index notation
    • +
    • Needed Generic Dimensions
    • +
  • +
  • cuTensorNet (CUDA) +
      +
    • Support for non-euclidean metric
    • +
    • Physical indices
    • +
  • +
  • ITensors.jl (Julia/C++) +
      +
    • Wanted Rust (for LU/gammaloop), zero-cost abstractions FTW
    • +
    • Does not talk Symbolica
    • +
  • +
+ +
+
+
+

Introducing Spenso!

+ +
    +
  • Provides two data storage layouts: +
      +
    • Dense (flat vector)
    • +
    • Sparse (hashmap based)
    • +
  • +
  • The components can be any type +
      +
    • Crucially Symbolica Atom
    • +
  • +
  • Can create tensor “networks” for deferred contraction
  • +
+
+
+

Initializing

+

Each tensor needs at minimum ae index Structure. The Structure encodes the indices of the tensor, including the representation and dimensionality

+
let mink = Representation::Lorentz(Dimension(4));
+let mu = Slot::from((AbstractIndex(0),mink));
+let bis = Representation::Bispinor(Dimension(4));
+let i = Slot::from((AbstractIndex(1),bis));
+let j = Slot::from((AbstractIndex(2),bis));
+
+let structure = NamedStructure::new(&[mu,i,j],"γ");
+

Represents: \[ +\gamma^{\mu}_{ij} +\]

+
+
+

Adding data

+

Now we can add data to this structure.

+
let iunit = Complex::<f64>::i();
+let mut gamma = SparseTensor::empty(structure);
+
+gamma.set(&[0, 0, 0], 1.);
+gamma.set(&[1, 1, 0], 1.);
+gamma.set(&[2, 2, 0], -1.);
+gamma.set(&[3, 3, 0], -1.);
+
+gamma.set(&[0, 3, 1], 1.);
+gamma.set(&[1, 2, 1], 1.);
+gamma.set(&[2, 1, 1], -1.);
+gamma.set(&[3, 0, 1], -1.);
+
+gamma.set(&[0, 3, 2], -iunit);
+gamma.set(&[1, 2, 2], iunit);
+gamma.set(&[2, 1, 2], iunit);
+gamma.set(&[3, 0, 2], -iunit);
+
+gamma.set(&[0, 2, 3], 1.);
+gamma.set(&[1, 3, 3], -1.);
+gamma.set(&[2, 0, 3], -1.);
+gamma.set(&[3, 1, 3], 1.);
+ +
+
+

What if I don’t yet know the data:

+

The tensor can be symbolic, or parametric!

+
let nu = Slot::from((AbstractIndex(1), mink));
+let structure = NamedStructure::from_slots(vec![mu, nu], "T");
+
+let symbolicT: SymbolicTensor = SymbolicTensor::from_named(&structure).unwrap();
+println!("{}", symbolicT); // T(lor(4,0),lor(4,1))
+let paramT: DenseTensor<Atom, _> = symbolicT.shadow().unwrap();
+let t12: Atom = paramT.get(&[1, 2]).unwrap());// T(1,2)
+
+
+

What can you do with tensors:

+
    +
  • Iterate along specific dimensions:

    +
    for (c, _, _) in gamma.fiber(&[true, false, true].into()).iter() {
    +        //stuff
    +}
  • +
  • Arithmetic, when structures match:

    +
      let 2gamma = gamma+gamma
  • +
  • Contractions, when indices match:

    +
      let cont = gamma.contract(&paramT);
  • +
+
+
+

Specific example

+

consider this triangle diagram:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

The numerator spinor structure looks like

+

\[p_1^{\mu_1} p_2^{\mu_2}p_3^{\mu_3} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_2} \gamma_\rho \gamma_{\mu_3})\]

+ +
+
+

Specific example

+

\[p_1^{\mu_1} p_2^{\mu_2}p_3^{\mu_3} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_2} \gamma_\rho \gamma_{\mu_3})\]

+

Usually we turn this into dot products by repeatedly applying the defining relation:

+

\[\left\{ \gamma_\mu, \gamma_\nu \right\} = \gamma_\mu \gamma_\nu + \gamma_\nu \gamma_\mu = 2 \eta_{\mu \nu} I_4\]

+
+
+

Specific example

+

\[2 p_1^{\mu_1} p_2^{\mu_2}p_{3,\rho} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_2}) \\ +-p_1^{\mu_1} p_2^{\mu_2}p_3^{\mu_3} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_2} \gamma_{\mu_3}\gamma_\rho)\]

+

Usually we turn this into dot products by repeatedly applying the defining relation:

+

\[\left\{ \gamma_\mu, \gamma_\nu \right\} = \gamma_\mu \gamma_\nu + \gamma_\nu \gamma_\mu = 2 \eta_{\mu \nu} I_4\]

+
+
+

Specific example

+

\[2 p_1^{\mu_1} p_2^{\mu_2}p_{3,\rho} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_2}) \\ +-2 p_2 \cdot p_3 p_1^{\mu_1} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_\rho) \\ ++p_1^{\mu_1} p_2^{\mu_2}p_3^{\mu_3} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_3}\gamma_{\mu_2} \gamma_\rho) +\]

+

Usually we turn this into dot products by repeatedly applying the defining relation:

+

\[\left\{ \gamma_\mu, \gamma_\nu \right\} = \gamma_\mu \gamma_\nu + \gamma_\nu \gamma_\mu = 2 \eta_{\mu \nu} I_4\]

+ +
+
+

Specific example

+

\[-4(p_1 \cdot p_2) p_{3,\mu}\eta_{\nu\rho} + \\ +4(p_1\cdot p_2)p_{3,\nu}\eta_{\mu\rho} + \\ -4(p_1\cdot p_2)p_{3,\rho}\eta_{\mu\nu} + \\ +4(p_1\cdot p_3)p_{2,\mu}\eta_{\nu\rho} + \\ -4(p_1\cdot p_3)p_{2,\nu}\eta_{\mu\rho} + \\ -4(p_1\cdot p_3)p_{2,\rho}\eta_{\mu\nu} + \\ -4p_{1,\mu} (p_2\cdot p_3)\eta_{\nu\rho} + \\ +4p_{1,\mu} p_{2,\nu}p_{3,\rho} + \\ +4p_{1,\mu} p_{2,\rho}p_{3,\nu} + \\ -4p_{1,\nu}(p_2\cdot p_3)\eta_{\mu\rho} + \\ +4p_{1,\nu}p_{2,\mu}p_{3,\rho} + \\ +4p_{1,\nu}p_{2,\rho}p_{3,\mu} + \\ +4p_{1,\rho}(p_2\cdot p_3)\eta_{\mu\nu} + \\ -4p_{1,\rho}p_{2,\mu}p_{3,\nu} + \\ +4p_{1,\rho}p_{2,\nu}p_{3,\mu} +\]

+
+
+

Problem

+

Exponential growth of the terms:

+
+
    +
  • length 6: 15
  • +
  • length 10: ~1000
  • +
+
+

If you do componentwise contraction on the unmodified trace:

+

\[p_1^{\mu_1} p_2^{\mu_2}p_3^{\mu_3} \mathrm{Tr} (\gamma_\mu \gamma_{\mu_1} \gamma_\nu \gamma_{\mu_2} \gamma_\rho \gamma_{\mu_3})\]

+

The scaling is linear in the chain length!

+
+
+

Deffered contraction

+

Consider a string of tensors

+

Example

+
+

\[\bar{v}_{i_1} u_{i_3}p_1^{\mu_1} p_2^{\mu_2}p_3^{\mu_3}p_4^{\mu_4}p_5^{\mu_5}p_6^{\mu_6}p_7^{\mu_7} \gamma_{i_1 i_2}^{\mu_1} +\\ \gamma_{i_2 i_3}^{\nu} +\gamma_{i_4 i_5}^{\nu} +\gamma_{i_5 i_6}^{\mu_2} +\gamma_{i_6 i_7}^{\mu_3} +\gamma_{i_7 i_8}^{\mu_4} +\gamma_{i_8 i_9}^{\mu_5} +\gamma_{i_9 i_4}^{\mu_6}\]

+
+
+
+

Deffered contraction

+

We can model a string of tensors, with repeated indices as a graph, each edge is an index, each node a tensor.

+

Example

+
+
+
+
+

+
+ + + + + +4294967299 + + + + +4294967297 + + + + +4294967299--4294967297 + + + + +4294967298 + + + + +4294967299--4294967298 + + + + +4294967301 + + + + +4294967301--4294967299 + + + + +4294967300 + + + + +4294967301--4294967300 + + + + +4294967302 + + + + +4294967302--4294967301 + + + + +4294967304 + + + + +4294967304--4294967302 + + + + +4294967303 + + + + +4294967304--4294967303 + + + + +4294967306 + + + + +4294967306--4294967304 + + + + +4294967305 + + + + +4294967306--4294967305 + + + + +4294967308 + + + + +4294967308--4294967306 + + + + +4294967307 + + + + +4294967308--4294967307 + + + + +4294967310 + + + + +4294967310--4294967308 + + + + +4294967309 + + + + +4294967310--4294967309 + + + + +4294967311 + + + + +4294967311--4294967302 + + + + +4294967311--4294967310 + + + + +4294967312 + + + + +4294967312--4294967311 + + + + +
+

+
+
+
+
+
+
+

Deffered contraction

+

We can model a string of tensors, with repeated indices as a graph, each edge is an index, each node a tensor.

+

Example

+
+
let γ1: MixedTensor<_> = gamma(1.into(), (1.into(), 2.into())).into();
+println!("{}", γ1.to_symbolic().unwrap()); 
+//γ(lor(4,1),spin(4,1),spina(4,2))
+let γ2: MixedTensor<_> = gamma(10.into(), (2.into(), 3.into())).into();
+// ...
+let p1: MixedTensor<_> = param_mink_four_vector(1.into(), "p1").into();
+/// ...
+let u: MixedTensor<_> = param_four_vector(1.into(), "u").into();
+println!("{}", u.to_symbolic().unwrap()); 
+// u(spin(4,1))
+
+let net: TensorNetwork<NumTensor> = TensorNetwork::from(vec![u,v,p1,p2,
+                                                            p3,p4,p5,p6,
+                                                            γ1,γ2,γ3,γ4,
+                                                            γ5,γ6,γ7,γ8]);
+
+println!("{}",net.dot());
+
+
+
+

Tensor Network

+
+
+
+
+
+

+
+ + + + + +4294967299 + + + + +4294967297 + + + + +4294967299--4294967297 + + + + +4294967298 + + + + +4294967299--4294967298 + + + + +4294967301 + + + + +4294967301--4294967299 + + + + +4294967300 + + + + +4294967301--4294967300 + + + + +4294967302 + + + + +4294967302--4294967301 + + + + +4294967304 + + + + +4294967304--4294967302 + + + + +4294967303 + + + + +4294967304--4294967303 + + + + +4294967306 + + + + +4294967306--4294967304 + + + + +4294967305 + + + + +4294967306--4294967305 + + + + +4294967308 + + + + +4294967308--4294967306 + + + + +4294967307 + + + + +4294967308--4294967307 + + + + +4294967310 + + + + +4294967310--4294967308 + + + + +4294967309 + + + + +4294967310--4294967309 + + + + +4294967311 + + + + +4294967311--4294967302 + + + + +4294967311--4294967310 + + + + +4294967312 + + + + +4294967312--4294967311 + + + + +
+

+
+
+
+
+
    +
  • We now essentially have a tensor network

  • +
  • Contraction needs to happen according to some algorithm

    +
    net.contract_algo(Self::edge_to_min_degree_node)
  • +
  • This can be custom.

  • +
  • Don’t need to contract all the way down

  • +
+
+
+
+
+

Shadowing

+
+
    +
  • If some tensors are fully parametric, there is a quick explosion of terms.
  • +
  • We can contract until a certain limit, then “shadow” the obtained network with parametric tensors
  • +
+
+
+
+
+
+
+

+
+ + + + + +4294967299 + + + + +4294967297 + + + + +4294967299--4294967297 + + + + +4294967298 + + + + +4294967299--4294967298 + + + + +4294967301 + + + + +4294967301--4294967299 + + + + +4294967300 + + + + +4294967301--4294967300 + + + + +4294967303 + + + + +4294967303--4294967301 + + + + +4294967302 + + + + +4294967303--4294967302 + + + + +4294967305 + + + + +4294967305--4294967303 + + + + +4294967304 + + + + +4294967305--4294967304 + + + + +4294967307 + + + + +4294967307--4294967305 + + + + +4294967306 + + + + +4294967307--4294967306 + + + + +4294967309 + + + + +4294967309--4294967307 + + + + +4294967308 + + + + +4294967309--4294967308 + + + + +4294967311 + + + + +4294967311--4294967309 + + + + +4294967310 + + + + +4294967311--4294967310 + + + + +4294967313 + + + + +4294967313--4294967311 + + + + +4294967312 + + + + +4294967313--4294967312 + + + + +4294967315 + + + + +4294967315--4294967313 + + + + +4294967314 + + + + +4294967315--4294967314 + + + + +4294967317 + + + + +4294967317--4294967315 + + + + +4294967316 + + + + +4294967317--4294967316 + + + + +4294967319 + + + + +4294967319--4294967317 + + + + +4294967318 + + + + +4294967319--4294967318 + + + + +4294967321 + + + + +4294967321--4294967319 + + + + +4294967320 + + + + +4294967321--4294967320 + + + + +4294967323 + + + + +4294967323--4294967321 + + + + +4294967322 + + + + +4294967323--4294967322 + + + + +4294967325 + + + + +4294967325--4294967323 + + + + +4294967324 + + + + +4294967325--4294967324 + + + + +4294967327 + + + + +4294967327--4294967325 + + + + +4294967326 + + + + +4294967327--4294967326 + + + + +4294967329 + + + + +4294967329--4294967327 + + + + +4294967328 + + + + +4294967329--4294967328 + + + + +4294967331 + + + + +4294967331--4294967329 + + + + +4294967330 + + + + +4294967331--4294967330 + + + + +4294967333 + + + + +4294967333--4294967331 + + + + +4294967332 + + + + +4294967333--4294967332 + + + + +4294967335 + + + + +4294967335--4294967333 + + + + +4294967334 + + + + +4294967335--4294967334 + + + + +4294967337 + + + + +4294967337--4294967335 + + + + +4294967336 + + + + +4294967337--4294967336 + + + + +4294967338 + + + + +4294967338--4294967337 + + + + +4294967340 + + + + +4294967340--4294967338 + + + + +4294967339 + + + + +4294967340--4294967339 + + + + +4294967342 + + + + +4294967342--4294967340 + + + + +4294967341 + + + + +4294967342--4294967341 + + + + +4294967344 + + + + +4294967344--4294967342 + + + + +4294967343 + + + + +4294967344--4294967343 + + + + +4294967346 + + + + +4294967346--4294967344 + + + + +4294967345 + + + + +4294967346--4294967345 + + + + +4294967348 + + + + +4294967348--4294967346 + + + + +4294967347 + + + + +4294967348--4294967347 + + + + +4294967350 + + + + +4294967350--4294967348 + + + + +4294967349 + + + + +4294967350--4294967349 + + + + +4294967352 + + + + +4294967352--4294967350 + + + + +4294967351 + + + + +4294967352--4294967351 + + + + +4294967354 + + + + +4294967354--4294967352 + + + + +4294967353 + + + + +4294967354--4294967353 + + + + +4294967356 + + + + +4294967356--4294967354 + + + + +4294967355 + + + + +4294967356--4294967355 + + + + +4294967358 + + + + +4294967358--4294967356 + + + + +4294967357 + + + + +4294967358--4294967357 + + + + +4294967360 + + + + +4294967360--4294967358 + + + + +4294967359 + + + + +4294967360--4294967359 + + + + +4294967362 + + + + +4294967362--4294967360 + + + + +4294967361 + + + + +4294967362--4294967361 + + + + +4294967364 + + + + +4294967364--4294967362 + + + + +4294967363 + + + + +4294967364--4294967363 + + + + +4294967366 + + + + +4294967366--4294967364 + + + + +4294967365 + + + + +4294967366--4294967365 + + + + +4294967368 + + + + +4294967368--4294967366 + + + + +4294967367 + + + + +4294967368--4294967367 + + + + +4294967370 + + + + +4294967370--4294967368 + + + + +4294967369 + + + + +4294967370--4294967369 + + + + +4294967372 + + + + +4294967372--4294967370 + + + + +4294967371 + + + + +4294967372--4294967371 + + + + +4294967374 + + + + +4294967374--4294967372 + + + + +4294967373 + + + + +4294967374--4294967373 + + + + +4294967376 + + + + +4294967376--4294967374 + + + + +4294967375 + + + + +4294967376--4294967375 + + + + +4294967378 + + + + +4294967378--4294967376 + + + + +4294967377 + + + + +4294967378--4294967377 + + + + +4294967380 + + + + +4294967380--4294967378 + + + + +4294967379 + + + + +4294967380--4294967379 + + + + +4294967382 + + + + +4294967382--4294967380 + + + + +4294967381 + + + + +4294967382--4294967381 + + + + +4294967384 + + + + +4294967384--4294967382 + + + + +4294967383 + + + + +4294967384--4294967383 + + + + +4294967386 + + + + +4294967386--4294967384 + + + + +4294967385 + + + + +4294967386--4294967385 + + + + +4294967387 + + + + +4294967387--4294967338 + + + + +4294967387--4294967386 + + + + +4294967388 + + + + +4294967388--4294967387 + + + + +
+

+
+
+
+
+
+
+
+

+
+ + + + + +30064771165 + + + + +12884901893 + + + + +30064771165--12884901893 + + + + +21474836491 + + + + +21474836491--30064771165 + + + + +21474836497 + + + + +21474836497--21474836491 + + + + +21474836503 + + + + +21474836503--21474836497 + + + + +21474836509 + + + + +21474836509--21474836503 + + + + +21474836515 + + + + +21474836515--21474836509 + + + + +12884901930 + + + + +12884901930--21474836515 + + + + +21474836526 + + + + +21474836526--12884901930 + + + + +21474836532 + + + + +21474836532--21474836526 + + + + +21474836538 + + + + +21474836538--21474836532 + + + + +21474836544 + + + + +21474836544--21474836538 + + + + +21474836550 + + + + +21474836550--21474836544 + + + + +21474836556 + + + + +21474836556--21474836550 + + + + +21474836562 + + + + +21474836562--21474836556 + + + + +12884901976 + + + + +12884901976--21474836562 + + + + +12884901978 + + + + +12884901978--12884901930 + + + + +12884901978--12884901976 + + + + +
+

+
+
+
+
+
+
+
+

+
+ + + + + +38654705757 + + + + +133143986267 + + + + +38654705757--133143986267 + + + + +30064771089 + + + + +30064771089--38654705757 + + + + +30064771101 + + + + +30064771101--30064771089 + + + + +21474836522 + + + + +21474836522--30064771101 + + + + +30064771124 + + + + +30064771124--21474836522 + + + + +30064771136 + + + + +30064771136--30064771124 + + + + +30064771148 + + + + +30064771148--30064771136 + + + + +12884901978 + + + + +12884901978--30064771101 + + + + +12884901978--30064771148 + + + + +
+

+
+
+
+
+
+
    +
  • A similar idea has already been done in cuTensorNet!
  • +
+
+
+

Synergy with symbolica

+

Spenso understands a specific format of symbolica expressions and can recognise known tensors with numerical counterparts:

+
let structure = NamedStructure::from_slots(vec![mu, i, j], "γ");
+let p_struct = NamedStructure::from_slots(vec![mu], "p");
+let t_struct = NamedStructure::from_slots(vec![i, j, k], "T");
+
+let gamma_sym = SymbolicTensor::from_named(&structure).unwrap();
+let p_sym = SymbolicTensor::from_named(&p_struct).unwrap();
+let t_sym = SymbolicTensor::from_named(&t_struct).unwrap();
+
+let f = gamma_sym.contract(&p_sym).unwrap().contract(&t_sym).unwrap();
+
+println!("{}", *f.get_atom()); 
+
p(lor(4,0))*γ(lor(4,0),bis(4,1),bis(4,2))*T(bis(4,1),bis(4,2),bis(4,3))
+
let net: TensorNetwork<MixedTensor> = f.to_network().unwrap();
+
+
+
+

+
+ + + + + +4294967298 + + + + +4294967297 + + + + +4294967298--4294967297 + + + + +4294967299 + + + + +4294967299--4294967298 + + + + +4294967299--4294967298 + + + + +ext4294967303 + + + +4294967299--ext4294967303 + + + + +
+

+
+
+
+
+
+

Performance

+
+
+
+
+
+

+
+ + + + + +4294967299 + + + + +4294967297 + + + + +4294967299--4294967297 + + + + +4294967298 + + + + +4294967299--4294967298 + + + + +4294967301 + + + + +4294967301--4294967299 + + + + +4294967300 + + + + +4294967301--4294967300 + + + + +4294967303 + + + + +4294967303--4294967301 + + + + +4294967302 + + + + +4294967303--4294967302 + + + + +4294967305 + + + + +4294967305--4294967303 + + + + +4294967304 + + + + +4294967305--4294967304 + + + + +4294967307 + + + + +4294967307--4294967305 + + + + +4294967306 + + + + +4294967307--4294967306 + + + + +4294967309 + + + + +4294967309--4294967307 + + + + +4294967308 + + + + +4294967309--4294967308 + + + + +4294967311 + + + + +4294967311--4294967309 + + + + +4294967310 + + + + +4294967311--4294967310 + + + + +4294967313 + + + + +4294967313--4294967311 + + + + +4294967312 + + + + +4294967313--4294967312 + + + + +4294967315 + + + + +4294967315--4294967313 + + + + +4294967314 + + + + +4294967315--4294967314 + + + + +4294967317 + + + + +4294967317--4294967315 + + + + +4294967316 + + + + +4294967317--4294967316 + + + + +4294967319 + + + + +4294967319--4294967317 + + + + +4294967318 + + + + +4294967319--4294967318 + + + + +4294967321 + + + + +4294967321--4294967319 + + + + +4294967320 + + + + +4294967321--4294967320 + + + + +4294967323 + + + + +4294967323--4294967321 + + + + +4294967322 + + + + +4294967323--4294967322 + + + + +4294967325 + + + + +4294967325--4294967323 + + + + +4294967324 + + + + +4294967325--4294967324 + + + + +4294967327 + + + + +4294967327--4294967325 + + + + +4294967326 + + + + +4294967327--4294967326 + + + + +4294967329 + + + + +4294967329--4294967327 + + + + +4294967328 + + + + +4294967329--4294967328 + + + + +4294967331 + + + + +4294967331--4294967329 + + + + +4294967330 + + + + +4294967331--4294967330 + + + + +4294967333 + + + + +4294967333--4294967331 + + + + +4294967332 + + + + +4294967333--4294967332 + + + + +4294967335 + + + + +4294967335--4294967333 + + + + +4294967334 + + + + +4294967335--4294967334 + + + + +4294967337 + + + + +4294967337--4294967335 + + + + +4294967336 + + + + +4294967337--4294967336 + + + + +4294967338 + + + + +4294967338--4294967337 + + + + +4294967340 + + + + +4294967340--4294967338 + + + + +4294967339 + + + + +4294967340--4294967339 + + + + +4294967342 + + + + +4294967342--4294967340 + + + + +4294967341 + + + + +4294967342--4294967341 + + + + +4294967344 + + + + +4294967344--4294967342 + + + + +4294967343 + + + + +4294967344--4294967343 + + + + +4294967346 + + + + +4294967346--4294967344 + + + + +4294967345 + + + + +4294967346--4294967345 + + + + +4294967348 + + + + +4294967348--4294967346 + + + + +4294967347 + + + + +4294967348--4294967347 + + + + +4294967350 + + + + +4294967350--4294967348 + + + + +4294967349 + + + + +4294967350--4294967349 + + + + +4294967352 + + + + +4294967352--4294967350 + + + + +4294967351 + + + + +4294967352--4294967351 + + + + +4294967354 + + + + +4294967354--4294967352 + + + + +4294967353 + + + + +4294967354--4294967353 + + + + +4294967356 + + + + +4294967356--4294967354 + + + + +4294967355 + + + + +4294967356--4294967355 + + + + +4294967358 + + + + +4294967358--4294967356 + + + + +4294967357 + + + + +4294967358--4294967357 + + + + +4294967360 + + + + +4294967360--4294967358 + + + + +4294967359 + + + + +4294967360--4294967359 + + + + +4294967362 + + + + +4294967362--4294967360 + + + + +4294967361 + + + + +4294967362--4294967361 + + + + +4294967364 + + + + +4294967364--4294967362 + + + + +4294967363 + + + + +4294967364--4294967363 + + + + +4294967366 + + + + +4294967366--4294967364 + + + + +4294967365 + + + + +4294967366--4294967365 + + + + +4294967368 + + + + +4294967368--4294967366 + + + + +4294967367 + + + + +4294967368--4294967367 + + + + +4294967370 + + + + +4294967370--4294967368 + + + + +4294967369 + + + + +4294967370--4294967369 + + + + +4294967372 + + + + +4294967372--4294967370 + + + + +4294967371 + + + + +4294967372--4294967371 + + + + +4294967374 + + + + +4294967374--4294967372 + + + + +4294967373 + + + + +4294967374--4294967373 + + + + +4294967376 + + + + +4294967376--4294967374 + + + + +4294967375 + + + + +4294967376--4294967375 + + + + +4294967378 + + + + +4294967378--4294967376 + + + + +4294967377 + + + + +4294967378--4294967377 + + + + +4294967380 + + + + +4294967380--4294967378 + + + + +4294967379 + + + + +4294967380--4294967379 + + + + +4294967382 + + + + +4294967382--4294967380 + + + + +4294967381 + + + + +4294967382--4294967381 + + + + +4294967384 + + + + +4294967384--4294967382 + + + + +4294967383 + + + + +4294967384--4294967383 + + + + +4294967386 + + + + +4294967386--4294967384 + + + + +4294967385 + + + + +4294967386--4294967385 + + + + +4294967387 + + + + +4294967387--4294967338 + + + + +4294967387--4294967386 + + + + +4294967388 + + + + +4294967388--4294967387 + + + + +
+

+
+
+
+
+ + + + + + + + + + + + + + + +
SpensoSpenso CompiledHardcoded Fortran
104 μs16 μs31μs
+

We can use a compiled version of the stacked shadowing using symbolica!

+
+
+
+
+

Outlook

+
    +
  • More tooling to deal with specific tensor index symmetries
  • +
  • Python bindings compatible with symbolica
  • +
  • GPU support
  • +
  • SIMD support
  • +
+
+ +
+

Thanks

+

If you want to check it out it is on crates.io:

+

https://crates.io/crates/spenso

+

And on github:

+

https://github.com/alphal00p/spenso

+
+

+ +
+
+
+