├── .gitignore ├── .project ├── .travis.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── rustfmt.toml └── src ├── coordinates.rs ├── lib.rs ├── macros.rs ├── metric.rs ├── tensors ├── mod.rs ├── tensor.rs └── variance.rs └── tests ├── basic.rs ├── coord_transform.rs └── mod.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | 13 | Cargo.lock 14 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | differential-geometry 4 | 5 | 6 | 7 | 8 | 9 | com.github.rustdt.ide.core.Builder 10 | 11 | 12 | 13 | 14 | 15 | com.github.rustdt.ide.core.nature 16 | 17 | 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | after_success: |- 3 | [ $TRAVIS_BRANCH = master ] && 4 | [ $TRAVIS_PULL_REQUEST = false ] && 5 | cargo doc && 6 | echo "" > target/doc/index.html && 7 | sudo pip install ghp-import && 8 | ghp-import -n target/doc && 9 | git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages 10 | env: 11 | global: 12 | secure: NvnxcDYHicHAmxdBKLkmXFL00UXOt3SJGvIBK8GjuPGTFQ5BetCdjrUlVhHj9QeJQ+RTp4KyahexFNRgOTIimAGKV1L+A5v018F6pLUuQrwm8iDDvk4lA2CF+hEx5++nnah+xj2EQfher2dSAU3HCmBhjxIfa7WFNsNBVaNsnsHvMAr1+9zL3Ls8Ee9J2E19hVDXYhXug1sJ84m9LzIEMP1Ywar8mnQ+UNw45TBaN7DXTHTbFBttEjnOaSt3IANwww5rpx/b+Gm7J6f02TsT0QshH8OxODzG9NBMjtd90bTor8MOnADw+X/5Vs3YPMFvc38ahX66gIx5kvdPNirqj4vCx8p5uiDwyaPpF2fTseMEMHc9To7dB8knAJNmn6CvuycqArqaONwAR2mj+RGh+ywDv69bed3FRUgNMoCNZAcJGybuT9P4KOg3N2CTvSPOWFhTI8Cgp2O1ysbLtYlZQbybTSUAkadZV1JP6iecoDZxXIP+67TVfsHmZY+5xRVB/3Jkcjj9m1FEWwym45SmsCHEjw3JFqdjUeUANXZC2l3mbDh+4R2yyd8hk6dpllVY0PWlFsnAxAmX0W032mSCT2wxd5fNCIBvcCZs+y/FIuzhgB0AlI5OStWbGY7nN9tY7nYgnIZV0eeeDkH3mFyI4L1HS2ONsPtWRJWbT07pSOA= 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "differential-geometry" 3 | version = "0.3.1" 4 | authors = [ "Bartłomiej Kamiński " ] 5 | description = "A library for differential-geometric calculations" 6 | license = "MIT" 7 | repository = "https://github.com/fizyk20/differential-geometry.git" 8 | edition = "2018" 9 | 10 | [lib] 11 | name = "diffgeom" 12 | crate-type = ["rlib", "dylib"] 13 | 14 | [dependencies] 15 | generic-array = "0.13" 16 | 17 | [dev-dependencies] 18 | rand = "0.5" 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Bartłomiej Kamiński 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Crates.io](https://img.shields.io/crates/v/differential-geometry.svg)](https://crates.io/crates/differential-geometry) 2 | [![Build Status](https://travis-ci.org/fizyk20/differential-geometry.svg?branch=master)](https://travis-ci.org/fizyk20/differential-geometry) 3 | # differential-geometry 4 | 5 | [Crate documentation](https://fizyk20.github.io/differential-geometry/diffgeom) 6 | 7 | This is a crate for differential-geometric calculations, like tensor calculus on manifolds etc. 8 | 9 | Features (version 0.1): 10 | - Defining coordinate systems and conversions between them. 11 | - Defining points on a manifold. 12 | - Defining tensors; tensor addition, subtraction, outer product, inner product, contraction, matrix inversion. -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | use_try_shorthand = true 3 | -------------------------------------------------------------------------------- /src/coordinates.rs: -------------------------------------------------------------------------------- 1 | //! Module containing basic types representing coordinate systems. 2 | 3 | use super::tensors::{ContravariantIndex, CovariantIndex, Matrix, Tensor}; 4 | use crate::typenum::consts::U2; 5 | use crate::typenum::uint::Unsigned; 6 | use crate::typenum::Pow; 7 | use generic_array::{ArrayLength, GenericArray}; 8 | use std::fmt; 9 | use std::ops::{Index, IndexMut}; 10 | 11 | /// `CoordinateSystem` marks a struct (usually a unit struct) as representing a coordinate system. 12 | pub trait CoordinateSystem: Sized { 13 | /// An associated type representing the dimension of the coordinate system 14 | type Dimension: Unsigned + ArrayLength + ArrayLength; 15 | 16 | /// Function returning a small value for purposes of numerical differentiation. 17 | /// What is considered a small value may depend on the point, hence the parameter. 18 | /// Returns just 0.01 by default. 19 | fn small(_: &Point) -> f64 { 20 | 0.01 21 | } 22 | 23 | /// Function returning the dimension 24 | fn dimension() -> usize { 25 | Self::Dimension::to_usize() 26 | } 27 | } 28 | 29 | /// Struct representing a point on the manifold. The information about the coordinate system 30 | /// is saved in the type parameter, so that only operations on objects belonging to the same 31 | /// coordinate system will be allowed. 32 | pub struct Point { 33 | /// The coordinates of the point. 34 | x: GenericArray, 35 | } 36 | 37 | impl Point 38 | where 39 | T: CoordinateSystem, 40 | { 41 | /// Creates a new point with coordinates described by the array 42 | pub fn new(coords: GenericArray) -> Point { 43 | Point { x: coords } 44 | } 45 | 46 | /// Creates a new point with coordinates passed in the slice 47 | pub fn from_slice(coords: &[f64]) -> Point { 48 | Point { 49 | x: GenericArray::clone_from_slice(coords), 50 | } 51 | } 52 | 53 | /// Returns the point's coordinates as an array 54 | pub fn coords_array(&self) -> &GenericArray { 55 | &self.x 56 | } 57 | } 58 | 59 | impl Clone for Point 60 | where 61 | T: CoordinateSystem, 62 | { 63 | fn clone(&self) -> Point { 64 | Point::new(self.x.clone()) 65 | } 66 | } 67 | 68 | impl Copy for Point 69 | where 70 | T: CoordinateSystem, 71 | >::ArrayType: Copy, 72 | { 73 | } 74 | 75 | impl Index for Point 76 | where 77 | T: CoordinateSystem, 78 | { 79 | type Output = f64; 80 | 81 | fn index(&self, idx: usize) -> &f64 { 82 | &self.x[idx] 83 | } 84 | } 85 | 86 | impl IndexMut for Point 87 | where 88 | T: CoordinateSystem, 89 | { 90 | fn index_mut(&mut self, idx: usize) -> &mut f64 { 91 | &mut self.x[idx] 92 | } 93 | } 94 | 95 | impl PartialEq> for Point 96 | where 97 | T: CoordinateSystem, 98 | { 99 | fn eq(&self, rhs: &Point) -> bool { 100 | (0..T::dimension()).all(|i| self[i] == rhs[i]) 101 | } 102 | } 103 | 104 | impl Eq for Point where T: CoordinateSystem {} 105 | 106 | impl fmt::Debug for Point 107 | where 108 | T: CoordinateSystem, 109 | { 110 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 111 | write!(f, "Point{:?}", &self.x) 112 | } 113 | } 114 | 115 | /// Trait used for conversions between different coordinate systems. Implementing `ConversionTo` 116 | /// for a `CoordinateSystem` will allow objects in that system to be converted to the system `T` 117 | /// (note that `T` also has to be a `CoordinateSystem`). 118 | pub trait ConversionTo: CoordinateSystem 119 | where 120 | T::Dimension: Pow, 121 | >::Output: ArrayLength, 122 | { 123 | /// Function converting the coordinates of a point. 124 | fn convert_point(p: &Point) -> Point; 125 | 126 | /// Function calculating a Jacobian at a point - that is, the matrix of derivatives 127 | /// of the coordinate conversions. 128 | /// 129 | /// This will be contracted with contravariant indices in the tensor. 130 | fn jacobian(p: &Point) -> Matrix { 131 | let d = Self::dimension(); 132 | let mut result = Matrix::zero(Self::convert_point(p)); 133 | let h = Self::small(p); 134 | 135 | for j in 0..d { 136 | let mut x = p.clone(); 137 | x[j] = x[j] - h; 138 | let y1 = Self::convert_point(&x); 139 | 140 | x[j] = x[j] + h * 2.0; 141 | let y2 = Self::convert_point(&x); 142 | 143 | for i in 0..d { 144 | // calculate dyi/dxj 145 | let index = [i, j]; 146 | result[&index[..]] = (y2[i] - y1[i]) / (2.0 * h); 147 | } 148 | } 149 | 150 | result 151 | } 152 | 153 | /// The inverse matrix of the Jacobian at a point. 154 | /// 155 | /// In conversions, it will be contracted with covariant indices. 156 | fn inv_jacobian(p: &Point) -> Tensor { 157 | ConversionTo::::jacobian(p).inverse().unwrap() 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | **diffgeom** is a crate aiming to leverage the Rust type system to provide 3 | a type-safe API for tensor calculus on arbitrary manifolds. 4 | 5 | What is tensor calculus? 6 | ======================== 7 | 8 | Tensors are, in a way, a generalized idea similar to vectors and matrices. They are 9 | multidimensional arrays of numbers, but not all such arrays are tensors. What makes 10 | them tensors is how they behave in coordinate transformations. The details are a 11 | topic for a whole academic lecture, so I won't go into them. What's important 12 | is that tensors can be used for describing properties of curved spaces and it is 13 | the intended use case of this crate. 14 | 15 | Problems 16 | ======== 17 | 18 | Unfortunately, Rust currently doesn't support generics over static values, so 19 | another representation of type-level numbers is required. In this crate one 20 | provided by [typenum](https://github.com/paholg/typenum) is being used. This 21 | makes it necessary to use a lot of trait bounds, which break the compiler in 22 | a few ways, so some operations require the usage of a pretty cumbersome syntax. 23 | 24 | Example 25 | ======= 26 | 27 | Below you can see a code sample presenting some simple operations. 28 | 29 | ``` 30 | # extern crate diffgeom; 31 | # extern crate generic_array; 32 | use std::ops::Mul; 33 | use generic_array::{GenericArray, ArrayLength}; 34 | use diffgeom::coordinates::{CoordinateSystem, Point}; 35 | use diffgeom::tensors::{Vector, Covector, Matrix, InnerProduct}; 36 | use generic_array::arr; 37 | use generic_array::typenum::consts::{U0, U1, U2}; 38 | 39 | fn main() { 40 | // First, a coordinate system must be defined 41 | struct SomeSystem; 42 | impl CoordinateSystem for SomeSystem { 43 | type Dimension = U2; // a two-dimensional coordinate system 44 | } 45 | 46 | // Each tensor should be anchored at a point, so let's create one 47 | let point = Point::::new(arr![f64; 0.0, 0.0]); 48 | 49 | // A vector can be defined like that: 50 | let vector = Vector::::new(point, arr![f64; 1.0, 2.0]); 51 | 52 | // There are also covectors 53 | let covector = Covector::::new(point, arr![f64; 2.0, 0.5]); 54 | 55 | // They can be multiplied, yielding a matrix 56 | let matrix = as Mul>>::mul(vector, covector); 57 | // Unfortunately this causes infinite recursion in the compiler: 58 | // let matrix = vector * covector; 59 | 60 | // They can be contracted 61 | let scalar = as InnerProduct, U0, U1>> 62 | ::inner_product(vector, covector); 63 | 64 | // scalars returned by tensor functions need to be dereffed to f64 65 | assert_eq!(*scalar, *matrix.trace::()); 66 | } 67 | ``` 68 | */ 69 | pub extern crate generic_array; 70 | pub use generic_array::typenum; 71 | 72 | pub mod coordinates; 73 | pub mod macros; 74 | pub mod metric; 75 | pub mod tensors; 76 | 77 | #[cfg(test)] 78 | mod tests; 79 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! mul { 3 | ($T1: ty, $T2: ty; $op1: expr, $op2: expr) => {{ 4 | use std::ops::Mul; 5 | <$T1 as Mul<$T2>>::mul($op1, $op2) 6 | }}; 7 | } 8 | 9 | #[macro_export] 10 | macro_rules! inner { 11 | ($T1: ty, $T2: ty; $I1: ty, $I2: ty; $op1: expr, $op2: expr) => {{ 12 | use $crate::tensors::InnerProduct; 13 | <$T1 as InnerProduct<$T2, $I1, $I2>>::inner_product($op1, $op2) 14 | }}; 15 | } 16 | -------------------------------------------------------------------------------- /src/metric.rs: -------------------------------------------------------------------------------- 1 | use super::coordinates::{CoordinateSystem, Point}; 2 | use super::tensors::{ 3 | ContravariantIndex, CovariantIndex, InnerProduct, InvTwoForm, Tensor, TwoForm, 4 | }; 5 | use crate::inner; 6 | use crate::typenum::consts::{U0, U1, U2, U3}; 7 | use crate::typenum::{Exp, Pow, Unsigned}; 8 | use generic_array::ArrayLength; 9 | 10 | /// Trait representing the metric properties of the coordinate system 11 | pub trait MetricSystem: CoordinateSystem 12 | where 13 | ::Dimension: Pow + Pow, 14 | Exp<::Dimension, U2>: ArrayLength, 15 | Exp<::Dimension, U3>: ArrayLength, 16 | { 17 | /// Returns the metric tensor at a given point. 18 | fn g(point: &Point) -> TwoForm; 19 | 20 | /// Returns the inverse metric tensor at a given point. 21 | /// 22 | /// The default implementation calculates the metric and then inverts it. A direct 23 | /// implementation may be desirable for more performance. 24 | fn inv_g(point: &Point) -> InvTwoForm { 25 | Self::g(point).inverse().unwrap() 26 | } 27 | 28 | /// Returns the partial derivatives of the metric at a given point. 29 | /// 30 | /// The default implementation calculates them numerically. A direct implementation 31 | /// may be desirable for performance. 32 | fn dg(point: &Point) -> Tensor { 33 | let d = Self::dimension(); 34 | let mut result = Tensor::zero(point.clone()); 35 | let h = Self::small(point); 36 | 37 | for j in 0..d { 38 | let mut x = point.clone(); 39 | x[j] = x[j] - h; 40 | let g1 = Self::g(&x); 41 | 42 | x[j] = x[j] + h * 2.0; 43 | let g2 = Self::g(&x); 44 | 45 | for coord in g1.iter_coords() { 46 | // calculate dg_i/dx^j 47 | let index = [coord[0], coord[1], j]; 48 | result[&index[..]] = (g2[&*coord] - g1[&*coord]) / (2.0 * h); 49 | } 50 | } 51 | 52 | result 53 | } 54 | 55 | /// Returns the covariant Christoffel symbols (with three lower indices). 56 | /// 57 | /// The default implementation calculates them from the metric. A direct implementation 58 | /// may be desirable for performance. 59 | fn covariant_christoffel( 60 | point: &Point, 61 | ) -> Tensor { 62 | let dg = Self::dg(point); 63 | let mut result = 64 | Tensor::::zero(point.clone()); 65 | 66 | for i in result.iter_coords() { 67 | result[&*i] = 68 | 0.5 * (dg[&*i] + dg[&[i[0], i[2], i[1]][..]] - dg[&[i[1], i[2], i[0]][..]]); 69 | } 70 | 71 | result 72 | } 73 | 74 | /// Returns the Christoffel symbols. 75 | /// 76 | /// The default implementation calculates them from the metric. A direct implementation 77 | /// may be desirable for performance. 78 | fn christoffel( 79 | point: &Point, 80 | ) -> Tensor { 81 | let ig = Self::inv_g(point); 82 | let gamma = Self::covariant_christoffel(point); 83 | 84 | as InnerProduct< 85 | Tensor, 86 | U1, 87 | U2, 88 | >>::inner_product(ig, gamma) 89 | } 90 | } 91 | 92 | impl Tensor 93 | where 94 | T: MetricSystem, 95 | T::Dimension: Pow + Pow + Pow + Unsigned, 96 | Exp: ArrayLength, 97 | Exp: ArrayLength, 98 | Exp: ArrayLength, 99 | { 100 | pub fn square(&self) -> f64 { 101 | let g = T::g(self.get_point()); 102 | let temp = inner!(_, _; U1, U2; g, self.clone()); 103 | *inner!(_, _; U0, U1; temp, self.clone()) 104 | } 105 | 106 | pub fn normalize(&mut self) { 107 | let len = self.square().abs().sqrt(); 108 | for i in 0..T::Dimension::to_usize() { 109 | self[i] /= len; 110 | } 111 | } 112 | } 113 | 114 | impl Tensor 115 | where 116 | T: MetricSystem, 117 | T::Dimension: Pow + Pow + Pow + Unsigned, 118 | Exp: ArrayLength, 119 | Exp: ArrayLength, 120 | Exp: ArrayLength, 121 | { 122 | pub fn square(&self) -> f64 { 123 | let g = T::inv_g(self.get_point()); 124 | let temp = inner!(_, _; U1, U2; g, self.clone()); 125 | *inner!(_, _; U0, U1; temp, self.clone()) 126 | } 127 | 128 | pub fn normalize(&mut self) { 129 | let len = self.square().abs().sqrt(); 130 | for i in 0..T::Dimension::to_usize() { 131 | self[i] /= len; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/tensors/mod.rs: -------------------------------------------------------------------------------- 1 | //! Module containing definitions of tensors and operations on them. 2 | mod tensor; 3 | mod variance; 4 | 5 | pub use self::tensor::{ 6 | Covector, InnerProduct, InvTwoForm, Matrix, Scalar, Tensor, TwoForm, Vector, 7 | }; 8 | pub use self::variance::{ 9 | Concat, Contract, Contracted, ContravariantIndex, CovariantIndex, IndexType, Joined, 10 | OtherIndex, TensorIndex, Variance, 11 | }; 12 | -------------------------------------------------------------------------------- /src/tensors/tensor.rs: -------------------------------------------------------------------------------- 1 | //! This module defines the `Tensor` type and all sorts of operations on it. 2 | 3 | use super::variance::{Concat, Contract, Contracted, Joined, OtherIndex}; 4 | use super::{ContravariantIndex, CovariantIndex, IndexType, TensorIndex, Variance}; 5 | use crate::coordinates::{ConversionTo, CoordinateSystem, Point}; 6 | use crate::typenum::consts::{B1, U2}; 7 | use crate::typenum::uint::Unsigned; 8 | use crate::typenum::{Add1, Exp, Pow, Same}; 9 | use generic_array::{ArrayLength, GenericArray}; 10 | use std::ops::{Add, AddAssign, Deref, DerefMut, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; 11 | use std::ops::{Index, IndexMut}; 12 | 13 | /// Struct representing a tensor. 14 | /// 15 | /// A tensor is anchored at a given point and has coordinates 16 | /// represented in the system defined by the generic parameter 17 | /// `T`. The variance of the tensor (meaning its rank and types 18 | /// of its indices) is defined by `V`. This allows Rust 19 | /// to decide at compile time whether two tensors are legal 20 | /// to be added / multiplied / etc. 21 | /// 22 | /// It is only OK to perform an operation on two tensors if 23 | /// they belong to the same coordinate system. 24 | pub struct Tensor 25 | where 26 | T::Dimension: Pow, 27 | Exp: ArrayLength, 28 | { 29 | p: Point, 30 | x: GenericArray>, 31 | } 32 | 33 | impl Clone for Tensor 34 | where 35 | T: CoordinateSystem, 36 | U: Variance, 37 | T::Dimension: Pow, 38 | Exp: ArrayLength, 39 | { 40 | fn clone(&self) -> Tensor { 41 | Tensor { 42 | p: self.p.clone(), 43 | x: self.x.clone(), 44 | } 45 | } 46 | } 47 | 48 | impl Copy for Tensor 49 | where 50 | T: CoordinateSystem, 51 | U: Variance, 52 | T::Dimension: Pow, 53 | >::ArrayType: Copy, 54 | Exp: ArrayLength, 55 | as ArrayLength>::ArrayType: Copy, 56 | { 57 | } 58 | 59 | /// A struct for iterating over the coordinates of a tensor. 60 | pub struct CoordIterator 61 | where 62 | U: Variance, 63 | U::Rank: ArrayLength, 64 | { 65 | started: bool, 66 | dimension: usize, 67 | cur_coord: GenericArray, 68 | } 69 | 70 | impl CoordIterator 71 | where 72 | U: Variance, 73 | U::Rank: ArrayLength, 74 | { 75 | pub fn new(dimension: usize) -> CoordIterator { 76 | CoordIterator { 77 | started: false, 78 | dimension: dimension, 79 | cur_coord: GenericArray::default(), 80 | } 81 | } 82 | } 83 | 84 | impl Iterator for CoordIterator 85 | where 86 | U: Variance, 87 | U::Rank: ArrayLength, 88 | { 89 | type Item = GenericArray; 90 | 91 | fn next(&mut self) -> Option { 92 | if !self.started { 93 | self.started = true; 94 | return Some(self.cur_coord.clone()); 95 | } 96 | 97 | // handle scalars 98 | if self.cur_coord.len() < 1 { 99 | return None; 100 | } 101 | 102 | let mut i = self.cur_coord.len() - 1; 103 | loop { 104 | self.cur_coord[i] += 1; 105 | if self.cur_coord[i] < self.dimension { 106 | break; 107 | } 108 | self.cur_coord[i] = 0; 109 | if i == 0 { 110 | return None; 111 | } 112 | i -= 1; 113 | } 114 | 115 | Some(self.cur_coord.clone()) 116 | } 117 | } 118 | 119 | impl Tensor 120 | where 121 | T: CoordinateSystem, 122 | V: Variance, 123 | T::Dimension: Pow, 124 | Exp: ArrayLength, 125 | { 126 | /// Returns the point at which the tensor is defined. 127 | pub fn get_point(&self) -> &Point { 128 | &self.p 129 | } 130 | 131 | /// Sets the point at which the tensor is defined. 132 | pub fn set_point(&mut self, p: Point) { 133 | self.p = p; 134 | } 135 | 136 | /// Returns the tensor's coordinates as an array 137 | pub fn coords_array(&self) -> &GenericArray> { 138 | &self.x 139 | } 140 | 141 | /// Converts a set of tensor indices passed as a slice into a single index 142 | /// for the internal array. 143 | /// 144 | /// The length of the slice (the number of indices) has to be compatible 145 | /// with the rank of the tensor. 146 | pub fn get_coord(i: &[usize]) -> usize { 147 | assert_eq!(i.len(), V::rank()); 148 | let dim = T::dimension(); 149 | let index = i.into_iter().fold(0, |res, idx| { 150 | assert!(*idx < dim); 151 | res * dim + idx 152 | }); 153 | index 154 | } 155 | 156 | /// Returns the variance of the tensor, that is, the list of the index types. 157 | /// A vector would return vec![Contravariant], a metric tensor: vec![Covariant, Covariant]. 158 | pub fn get_variance() -> Vec { 159 | V::variance() 160 | } 161 | 162 | /// Returns the rank of the tensor 163 | pub fn get_rank() -> usize { 164 | V::rank() 165 | } 166 | 167 | /// Returns the number of coordinates of the tensor (equal to [Dimension]^[Rank]) 168 | pub fn get_num_coords() -> usize { 169 | >::Output::to_usize() 170 | } 171 | 172 | /// Creates a new, zero tensor at a given point 173 | pub fn zero(point: Point) -> Tensor { 174 | Tensor { 175 | p: point, 176 | x: GenericArray::default(), 177 | } 178 | } 179 | 180 | /// Creates a tensor at a given point with the coordinates defined by the array. 181 | /// 182 | /// The number of elements in the array must be equal to the number of coordinates 183 | /// of the tensor. 184 | /// 185 | /// One-dimensional array represents an n-dimensional tensor in such a way, that 186 | /// the last index is the one that is changing the most often, i.e. the sequence is 187 | /// as follows: 188 | /// (0,0,...,0), (0,0,...,1), (0,0,...,2), ..., (0,0,...,1,0), (0,0,...,1,1), ... etc. 189 | pub fn new( 190 | point: Point, 191 | coords: GenericArray>, 192 | ) -> Tensor { 193 | Tensor { 194 | p: point, 195 | x: coords, 196 | } 197 | } 198 | 199 | /// Creates a tensor at a given point with the coordinates defined by the slice. 200 | /// 201 | /// The number of elements in the slice must be equal to the number of coordinates 202 | /// of the tensor. 203 | /// 204 | /// One-dimensional slice represents an n-dimensional tensor in such a way, that 205 | /// the last index is the one that is changing the most often, i.e. the sequence is 206 | /// as follows: 207 | /// (0,0,...,0), (0,0,...,1), (0,0,...,2), ..., (0,0,...,1,0), (0,0,...,1,1), ... etc. 208 | pub fn from_slice(point: Point, slice: &[f64]) -> Tensor { 209 | assert_eq!(Tensor::::get_num_coords(), slice.len()); 210 | Tensor { 211 | p: point, 212 | x: GenericArray::clone_from_slice(slice), 213 | } 214 | } 215 | 216 | /// Contracts two indices 217 | /// 218 | /// The indices must be of opposite types. This is checked at compile time. 219 | pub fn trace(&self) -> Tensor> 220 | where 221 | Ul: Unsigned, 222 | Uh: Unsigned, 223 | V: Contract, 224 | as Variance>::Rank: ArrayLength, 225 | T::Dimension: Pow< as Variance>::Rank>, 226 | Exp as Variance>::Rank>: ArrayLength, 227 | { 228 | let index1 = Ul::to_usize(); 229 | let index2 = Uh::to_usize(); 230 | let rank = V::Rank::to_usize(); 231 | let dim = T::Dimension::to_usize(); 232 | 233 | let mut result = Tensor::>::zero(self.p.clone()); 234 | let num_coords_result = Tensor::>::get_num_coords(); 235 | let modh = dim.pow((rank - 1 - index2) as u32); 236 | let modl = dim.pow((rank - 2 - index1) as u32); 237 | 238 | for coord in 0..num_coords_result { 239 | let coord1 = coord / modl; 240 | let coord1rest = coord % modl; 241 | let coord2 = coord1rest / modh; 242 | let coord2rest = coord1rest % modh; 243 | let coord_template = coord1 * modl * dim * dim + coord2 * modh * dim + coord2rest; 244 | let mut sum = 0.0; 245 | 246 | for i in 0..T::dimension() { 247 | sum += self[coord_template + i * modl * dim + i * modh]; 248 | } 249 | 250 | result[coord] = sum; 251 | } 252 | 253 | result 254 | } 255 | } 256 | 257 | impl Tensor 258 | where 259 | T: CoordinateSystem, 260 | U: Variance, 261 | U::Rank: ArrayLength, 262 | T::Dimension: Pow, 263 | Exp: ArrayLength, 264 | { 265 | /// Returns an iterator over the coordinates of the tensor. 266 | pub fn iter_coords(&self) -> CoordIterator { 267 | CoordIterator::new(T::dimension()) 268 | } 269 | } 270 | 271 | impl<'a, T, U> Index<&'a [usize]> for Tensor 272 | where 273 | T: CoordinateSystem, 274 | U: Variance, 275 | T::Dimension: Pow, 276 | Exp: ArrayLength, 277 | { 278 | type Output = f64; 279 | 280 | fn index(&self, idx: &'a [usize]) -> &f64 { 281 | &self.x[Self::get_coord(idx)] 282 | } 283 | } 284 | 285 | impl<'a, T, U> IndexMut<&'a [usize]> for Tensor 286 | where 287 | T: CoordinateSystem, 288 | U: Variance, 289 | T::Dimension: Pow, 290 | Exp: ArrayLength, 291 | { 292 | fn index_mut(&mut self, idx: &'a [usize]) -> &mut f64 { 293 | &mut self.x[Self::get_coord(idx)] 294 | } 295 | } 296 | 297 | impl<'a, T, U> Index for Tensor 298 | where 299 | T: CoordinateSystem, 300 | U: Variance, 301 | T::Dimension: Pow, 302 | Exp: ArrayLength, 303 | { 304 | type Output = f64; 305 | 306 | fn index(&self, idx: usize) -> &f64 { 307 | &self.x[idx] 308 | } 309 | } 310 | 311 | impl<'a, T, U> IndexMut for Tensor 312 | where 313 | T: CoordinateSystem, 314 | U: Variance, 315 | T::Dimension: Pow, 316 | Exp: ArrayLength, 317 | { 318 | fn index_mut(&mut self, idx: usize) -> &mut f64 { 319 | &mut self.x[idx] 320 | } 321 | } 322 | 323 | /// A scalar type, which is a tensor with rank 0. 324 | /// 325 | /// This is de facto just a number, so it implements `Deref` and `DerefMut` into `f64`. 326 | pub type Scalar = Tensor; 327 | 328 | /// A vector type (rank 1 contravariant tensor) 329 | pub type Vector = Tensor; 330 | 331 | /// A covector type (rank 1 covariant tensor) 332 | pub type Covector = Tensor; 333 | 334 | /// A matrix type (rank 2 contravariant-covariant tensor) 335 | pub type Matrix = Tensor; 336 | 337 | /// A bilinear form type (rank 2 doubly covariant tensor) 338 | pub type TwoForm = Tensor; 339 | 340 | /// A rank 2 doubly contravariant tensor 341 | pub type InvTwoForm = Tensor; 342 | 343 | impl Deref for Scalar { 344 | type Target = f64; 345 | 346 | fn deref(&self) -> &f64 { 347 | &self.x[0] 348 | } 349 | } 350 | 351 | impl DerefMut for Scalar { 352 | fn deref_mut(&mut self) -> &mut f64 { 353 | &mut self.x[0] 354 | } 355 | } 356 | 357 | // Arithmetic operations 358 | 359 | impl AddAssign> for Tensor 360 | where 361 | T: CoordinateSystem, 362 | U: Variance, 363 | T::Dimension: Pow, 364 | Exp: ArrayLength, 365 | { 366 | fn add_assign(&mut self, rhs: Tensor) { 367 | assert!(self.p == rhs.p); 368 | for i in 0..(Tensor::::get_num_coords()) { 369 | self[i] += rhs[i]; 370 | } 371 | } 372 | } 373 | 374 | impl Add> for Tensor 375 | where 376 | T: CoordinateSystem, 377 | U: Variance, 378 | T::Dimension: Pow, 379 | Exp: ArrayLength, 380 | { 381 | type Output = Tensor; 382 | 383 | fn add(mut self, rhs: Tensor) -> Tensor { 384 | self += rhs; 385 | self 386 | } 387 | } 388 | 389 | impl SubAssign> for Tensor 390 | where 391 | T: CoordinateSystem, 392 | U: Variance, 393 | T::Dimension: Pow, 394 | Exp: ArrayLength, 395 | { 396 | fn sub_assign(&mut self, rhs: Tensor) { 397 | assert!(self.p == rhs.p); 398 | for i in 0..(Tensor::::get_num_coords()) { 399 | self[i] -= rhs[i]; 400 | } 401 | } 402 | } 403 | 404 | impl Sub> for Tensor 405 | where 406 | T: CoordinateSystem, 407 | U: Variance, 408 | T::Dimension: Pow, 409 | Exp: ArrayLength, 410 | { 411 | type Output = Tensor; 412 | 413 | fn sub(mut self, rhs: Tensor) -> Tensor { 414 | self -= rhs; 415 | self 416 | } 417 | } 418 | 419 | impl MulAssign for Tensor 420 | where 421 | T: CoordinateSystem, 422 | U: Variance, 423 | T::Dimension: Pow, 424 | Exp: ArrayLength, 425 | { 426 | fn mul_assign(&mut self, rhs: f64) { 427 | for i in 0..(Tensor::::get_num_coords()) { 428 | self[i] *= rhs; 429 | } 430 | } 431 | } 432 | 433 | impl Mul for Tensor 434 | where 435 | T: CoordinateSystem, 436 | U: Variance, 437 | T::Dimension: Pow, 438 | Exp: ArrayLength, 439 | { 440 | type Output = Tensor; 441 | 442 | fn mul(mut self, rhs: f64) -> Tensor { 443 | self *= rhs; 444 | self 445 | } 446 | } 447 | 448 | impl Mul> for f64 449 | where 450 | T: CoordinateSystem, 451 | U: Variance, 452 | T::Dimension: Pow, 453 | Exp: ArrayLength, 454 | { 455 | type Output = Tensor; 456 | 457 | fn mul(self, mut rhs: Tensor) -> Tensor { 458 | rhs *= self; 459 | rhs 460 | } 461 | } 462 | 463 | impl DivAssign for Tensor 464 | where 465 | T: CoordinateSystem, 466 | U: Variance, 467 | T::Dimension: Pow, 468 | Exp: ArrayLength, 469 | { 470 | fn div_assign(&mut self, rhs: f64) { 471 | for i in 0..(Tensor::::get_num_coords()) { 472 | self[i] /= rhs; 473 | } 474 | } 475 | } 476 | 477 | impl Div for Tensor 478 | where 479 | T: CoordinateSystem, 480 | U: Variance, 481 | T::Dimension: Pow, 482 | Exp: ArrayLength, 483 | { 484 | type Output = Tensor; 485 | 486 | fn div(mut self, rhs: f64) -> Tensor { 487 | self /= rhs; 488 | self 489 | } 490 | } 491 | 492 | // Tensor multiplication 493 | 494 | // For some reason this triggers recursion overflow when tested - to be investigated 495 | impl Mul> for Tensor 496 | where 497 | T: CoordinateSystem, 498 | U: Variance, 499 | V: Variance, 500 | U::Rank: ArrayLength, 501 | V::Rank: ArrayLength, 502 | T::Dimension: Pow + Pow, 503 | Exp: ArrayLength, 504 | Exp: ArrayLength, 505 | U: Concat, 506 | Joined: Variance, 507 | T::Dimension: Pow< as Variance>::Rank>, 508 | Exp as Variance>::Rank>: ArrayLength, 509 | { 510 | type Output = Tensor>; 511 | 512 | fn mul(self, rhs: Tensor) -> Tensor> { 513 | assert!(self.p == rhs.p); 514 | let mut result = Tensor::zero(self.p.clone()); 515 | let num_coords2 = Tensor::::get_num_coords(); 516 | let num_coords_result = Tensor::>::get_num_coords(); 517 | for coord in 0..num_coords_result { 518 | let coord1 = coord / num_coords2; 519 | let coord2 = coord % num_coords2; 520 | result[coord] = self[coord1] * rhs[coord2]; 521 | } 522 | result 523 | } 524 | } 525 | 526 | /// Trait representing the inner product of two tensors. 527 | /// 528 | /// The inner product is just a multiplication followed by a contraction. 529 | /// The contraction is defined by type parameters `Ul` and `Uh`. `Ul` has to 530 | /// be less than `Uh` and the indices at those positions must be of opposite types 531 | /// (checked at compile time) 532 | pub trait InnerProduct { 533 | type Output; 534 | 535 | fn inner_product(self, rhs: Rhs) -> Self::Output; 536 | } 537 | 538 | impl InnerProduct, Ul, Uh> for Tensor 539 | where 540 | T: CoordinateSystem, 541 | U: Variance, 542 | V: Variance, 543 | Ul: Unsigned, 544 | Uh: Unsigned, 545 | T::Dimension: Pow + Pow, 546 | Exp: ArrayLength, 547 | Exp: ArrayLength, 548 | U: Concat, 549 | Joined: Contract, 550 | , Ul, Uh> as Variance>::Rank: ArrayLength, 551 | T::Dimension: Pow<, Ul, Uh> as Variance>::Rank>, 552 | Exp, Ul, Uh> as Variance>::Rank>: ArrayLength, 553 | { 554 | type Output = Tensor, Ul, Uh>>; 555 | 556 | fn inner_product(self, rhs: Tensor) -> Tensor, Ul, Uh>> { 557 | assert_eq!(self.p, rhs.p); 558 | let indexl = Ul::to_usize(); 559 | let indexh = Uh::to_usize(); 560 | let num_coords_result = Tensor::, Ul, Uh>>::get_num_coords(); 561 | let u_rank = U::Rank::to_usize(); 562 | let v_rank = V::Rank::to_usize(); 563 | let dim = T::Dimension::to_usize(); 564 | 565 | let mut result = Tensor::, Ul, Uh>>::zero(self.p.clone()); 566 | let (modl, modh, modv) = match (indexl < u_rank, indexh < u_rank) { 567 | (true, true) => ( 568 | dim.pow((u_rank - 2 - indexl) as u32), 569 | dim.pow((u_rank - 1 - indexh) as u32), 570 | dim.pow(v_rank as u32), 571 | ), 572 | (true, false) => ( 573 | dim.pow((u_rank - 1 - indexl) as u32), 574 | dim.pow((u_rank + v_rank - 1 - indexh) as u32), 575 | dim.pow((v_rank - 1) as u32), 576 | ), 577 | (false, false) => ( 578 | dim.pow((u_rank + v_rank - 2 - indexl) as u32), 579 | dim.pow((u_rank + v_rank - 1 - indexh) as u32), 580 | dim.pow(v_rank as u32), 581 | ), 582 | _ => unreachable!(), 583 | }; 584 | 585 | let to_templates_both1 = |coord| { 586 | let coords1 = coord / modv; 587 | let coords2 = coord % modv; 588 | let coords1part1 = coords1 / modl; 589 | let coords1part2 = (coords1 % modl) / modh; 590 | let coords1part3 = coords1 % modh; 591 | ( 592 | coords1part1 * modl * dim * dim + coords1part2 * modh * dim + coords1part3, 593 | coords2, 594 | modl + modh, 595 | 0, 596 | ) 597 | }; 598 | 599 | let to_templates_both2 = |coord| { 600 | let coords1 = coord / modv; 601 | let coords2 = coord % modv; 602 | let coords2part1 = coords2 / modl; 603 | let coords2part2 = (coords2 % modl) / modh; 604 | let coords2part3 = coords2 % modh; 605 | ( 606 | coords1, 607 | coords2part1 * modl * dim * dim + coords2part2 * modh * dim + coords2part3, 608 | 0, 609 | modl + modh, 610 | ) 611 | }; 612 | 613 | let to_templates = |coord| { 614 | let coords1 = coord / modv; 615 | let coords2 = coord % modv; 616 | let coords1part1 = coords1 / modl; 617 | let coords1part2 = coords1 % modl; 618 | let coords2part1 = coords2 / modh; 619 | let coords2part2 = coords2 % modh; 620 | ( 621 | coords1part1 * modl * dim + coords1part2, 622 | coords2part1 * modh * dim + coords2part2, 623 | modl, 624 | modh, 625 | ) 626 | }; 627 | 628 | let templates: &Fn(usize) -> (usize, usize, usize, usize) = 629 | match (indexl < u_rank, indexh < u_rank) { 630 | (false, false) => &to_templates_both2, 631 | (true, false) => &to_templates, 632 | (true, true) => &to_templates_both1, 633 | _ => unreachable!(), 634 | }; 635 | 636 | for coord in 0..num_coords_result { 637 | let mut sum = 0.0; 638 | let (mut coord1, mut coord2, step1, step2) = templates(coord); 639 | for _ in 0..dim { 640 | sum += self[coord1] * rhs[coord2]; 641 | coord1 += step1; 642 | coord2 += step2; 643 | } 644 | result[coord] = sum; 645 | } 646 | 647 | result 648 | } 649 | } 650 | 651 | impl Tensor 652 | where 653 | T: CoordinateSystem, 654 | Ul: TensorIndex + OtherIndex, 655 | Ur: TensorIndex + OtherIndex, 656 | Add1: Unsigned + Add, 657 | Add1: Unsigned + Add, 658 | Add1<<
    ::Output as Variance>::Rank>: Unsigned + Add, 659 | Add1<<::Output as Variance>::Rank>: Unsigned + Add, 660 | <(Ul, Ur) as Variance>::Rank: ArrayLength, 661 | T::Dimension: Pow> + Pow> + ArrayLength, 662 | T::Dimension: Pow::Output as Variance>::Rank>>, 663 | T::Dimension: Pow::Output as Variance>::Rank>>, 664 | Exp>: ArrayLength, 665 | Exp>: ArrayLength, 666 | Exp::Output as Variance>::Rank>>: ArrayLength, 667 | Exp::Output as Variance>::Rank>>: ArrayLength, 668 | { 669 | /// Returns a unit matrix (1 on the diagonal, 0 everywhere else) 670 | pub fn unit(p: Point) -> Tensor { 671 | let mut result = Tensor::::zero(p); 672 | 673 | for i in 0..T::dimension() { 674 | let coords: &[usize] = &[i, i]; 675 | result[coords] = 1.0; 676 | } 677 | 678 | result 679 | } 680 | 681 | /// Transposes the matrix 682 | pub fn transpose(&self) -> Tensor { 683 | let mut result = Tensor::::zero(self.p.clone()); 684 | 685 | for coords in self.iter_coords() { 686 | let coords2: &[usize] = &[coords[1], coords[0]]; 687 | result[coords2] = self[&*coords]; 688 | } 689 | 690 | result 691 | } 692 | 693 | // Function calculating the LU decomposition of a matrix - found in the internet 694 | // The decomposition is done in-place and a permutation vector is returned (or None 695 | // if the matrix was singular) 696 | fn lu_decompose(&mut self) -> Option> { 697 | let n = T::dimension(); 698 | let absmin = 1.0e-30_f64; 699 | let mut result = GenericArray::default(); 700 | let mut row_norm = GenericArray::::default(); 701 | 702 | let mut max_row = 0; 703 | 704 | for i in 0..n { 705 | let mut absmax = 0.0; 706 | 707 | for j in 0..n { 708 | let coord: &[usize] = &[i, j]; 709 | let maxtemp = self[coord].abs(); 710 | absmax = if maxtemp > absmax { maxtemp } else { absmax }; 711 | } 712 | 713 | if absmax == 0.0 { 714 | return None; 715 | } 716 | 717 | row_norm[i] = 1.0 / absmax; 718 | } 719 | 720 | for j in 0..n { 721 | for i in 0..j { 722 | for k in 0..i { 723 | let coord1: &[usize] = &[i, j]; 724 | let coord2: &[usize] = &[i, k]; 725 | let coord3: &[usize] = &[k, j]; 726 | 727 | self[coord1] -= self[coord2] * self[coord3]; 728 | } 729 | } 730 | 731 | let mut absmax = 0.0; 732 | 733 | for i in j..n { 734 | let coord1: &[usize] = &[i, j]; 735 | 736 | for k in 0..j { 737 | let coord2: &[usize] = &[i, k]; 738 | let coord3: &[usize] = &[k, j]; 739 | 740 | self[coord1] -= self[coord2] * self[coord3]; 741 | } 742 | 743 | let maxtemp = self[coord1].abs() * row_norm[i]; 744 | 745 | if maxtemp > absmax { 746 | absmax = maxtemp; 747 | max_row = i; 748 | } 749 | } 750 | 751 | if max_row != j { 752 | if (j == n - 2) && self[&[j, j + 1] as &[usize]] == 0.0 { 753 | max_row = j; 754 | } else { 755 | for k in 0..n { 756 | let jk: &[usize] = &[j, k]; 757 | let maxrow_k: &[usize] = &[max_row, k]; 758 | let maxtemp = self[jk]; 759 | self[jk] = self[maxrow_k]; 760 | self[maxrow_k] = maxtemp; 761 | } 762 | 763 | row_norm[max_row] = row_norm[j]; 764 | } 765 | } 766 | 767 | result[j] = max_row; 768 | 769 | let jj: &[usize] = &[j, j]; 770 | 771 | if self[jj] == 0.0 { 772 | self[jj] = absmin; 773 | } 774 | 775 | if j != n - 1 { 776 | let maxtemp = 1.0 / self[jj]; 777 | for i in j + 1..n { 778 | self[&[i, j] as &[usize]] *= maxtemp; 779 | } 780 | } 781 | } 782 | 783 | Some(result) 784 | } 785 | 786 | // Function solving a linear system of equations (self*x = b) using the LU decomposition 787 | fn lu_substitution( 788 | &self, 789 | b: &GenericArray, 790 | permute: &GenericArray, 791 | ) -> GenericArray { 792 | let mut result = b.clone(); 793 | let n = T::dimension(); 794 | 795 | for i in 0..n { 796 | let mut tmp = result[permute[i]]; 797 | result[permute[i]] = result[i]; 798 | for j in (0..i).rev() { 799 | tmp -= self[&[i, j] as &[usize]] * result[j]; 800 | } 801 | result[i] = tmp; 802 | } 803 | 804 | for i in (0..n).rev() { 805 | for j in i + 1..n { 806 | result[i] -= self[&[i, j] as &[usize]] * result[j]; 807 | } 808 | result[i] /= self[&[i, i] as &[usize]]; 809 | } 810 | 811 | result 812 | } 813 | 814 | /// Function calculating the inverse of `self` using the LU ddecomposition. 815 | /// 816 | /// The return value is an `Option`, since `self` may be non-invertible - 817 | /// in such a case, None is returned 818 | pub fn inverse( 819 | &self, 820 | ) -> Option::Output, ::Output)>> { 821 | let mut result = 822 | Tensor::::Output, ::Output)>::zero( 823 | self.p.clone(), 824 | ); 825 | 826 | let mut tmp = self.clone(); 827 | 828 | let permute = match tmp.lu_decompose() { 829 | Some(p) => p, 830 | None => return None, 831 | }; 832 | 833 | for i in 0..T::dimension() { 834 | let mut dxm = GenericArray::::default(); 835 | dxm[i] = 1.0; 836 | 837 | let x = tmp.lu_substitution(&dxm, &permute); 838 | 839 | for k in 0..T::dimension() { 840 | result[&[k, i] as &[usize]] = x[k]; 841 | } 842 | } 843 | 844 | Some(result) 845 | } 846 | } 847 | 848 | impl Tensor 849 | where 850 | T: CoordinateSystem, 851 | U: Variance, 852 | U::Rank: ArrayLength, 853 | T::Dimension: Pow, 854 | Exp: ArrayLength, 855 | { 856 | pub fn convert(&self) -> Tensor 857 | where 858 | T2: CoordinateSystem + 'static, 859 | T2::Dimension: Pow + Pow + Same, 860 | Exp: ArrayLength, 861 | Exp: ArrayLength, 862 | T: ConversionTo, 863 | { 864 | let mut result = Tensor::::zero(>::convert_point(&self.p)); 865 | 866 | let jacobian = >::jacobian(&self.p); 867 | let inv_jacobian = >::inv_jacobian(&self.p); 868 | let variance = ::variance(); 869 | 870 | for i in result.iter_coords() { 871 | let mut temp = 0.0; 872 | for j in self.iter_coords() { 873 | let mut temp2 = self[&*j]; 874 | for (k, v) in variance.iter().enumerate() { 875 | let coords = [i[k], j[k]]; 876 | temp2 *= match *v { 877 | IndexType::Covariant => inv_jacobian[&coords[..]], 878 | IndexType::Contravariant => jacobian[&coords[..]], 879 | }; 880 | } 881 | temp += temp2; 882 | } 883 | result[&*i] = temp; 884 | } 885 | 886 | result 887 | } 888 | } 889 | -------------------------------------------------------------------------------- /src/tensors/variance.rs: -------------------------------------------------------------------------------- 1 | //! Module defining variances (types of tensors) 2 | 3 | use crate::typenum::bit::Bit; 4 | use crate::typenum::consts::{B1, U0, U1}; 5 | use crate::typenum::uint::{UInt, Unsigned}; 6 | use crate::typenum::{Add1, Sub1}; 7 | use crate::typenum::{Cmp, Greater, Same}; 8 | use std::ops::{Add, Sub}; 9 | 10 | /// This enum serves to represent the type of a tensor. A tensor can have any number of indices, 11 | /// and each one can be either covariant (a lower index), or contravariant (an upper index). 12 | /// For example, a vector is a tensor with only one contravariant index. 13 | #[derive(Clone, Copy, PartialEq, Debug)] 14 | pub enum IndexType { 15 | Covariant, 16 | Contravariant, 17 | } 18 | 19 | /// Trait identifying a type as representing a tensor variance. It is implemented for 20 | /// `CovariantIndex`, `ContravariantIndex` and tuples (Index, Variance). 21 | pub trait Variance { 22 | type Rank: Unsigned + Add; 23 | fn rank() -> usize { 24 | Self::Rank::to_usize() 25 | } 26 | fn variance() -> Vec; 27 | } 28 | 29 | impl Variance for () { 30 | type Rank = U0; 31 | 32 | fn variance() -> Vec { 33 | vec![] 34 | } 35 | } 36 | 37 | /// Trait identifying a type as representing a tensor index. It is implemented 38 | /// for `CovariantIndex` and `ContravariantIndex`. 39 | pub trait TensorIndex: Variance { 40 | fn index_type() -> IndexType; 41 | } 42 | 43 | /// Type representing a contravariant (upper) tensor index. 44 | pub struct ContravariantIndex; 45 | impl TensorIndex for ContravariantIndex { 46 | fn index_type() -> IndexType { 47 | IndexType::Contravariant 48 | } 49 | } 50 | 51 | /// Type representing a covariant (lower) tensor index. 52 | pub struct CovariantIndex; 53 | impl TensorIndex for CovariantIndex { 54 | fn index_type() -> IndexType { 55 | IndexType::Covariant 56 | } 57 | } 58 | 59 | impl Variance for ContravariantIndex { 60 | type Rank = U1; 61 | fn variance() -> Vec { 62 | vec![IndexType::Contravariant] 63 | } 64 | } 65 | 66 | impl Variance for CovariantIndex { 67 | type Rank = U1; 68 | fn variance() -> Vec { 69 | vec![IndexType::Covariant] 70 | } 71 | } 72 | 73 | /// Trait representing the other index type 74 | /// 75 | /// Used for identifying indices that can be contracted 76 | pub trait OtherIndex: TensorIndex { 77 | type Output: TensorIndex; 78 | } 79 | 80 | impl OtherIndex for CovariantIndex { 81 | type Output = ContravariantIndex; 82 | } 83 | 84 | impl OtherIndex for ContravariantIndex { 85 | type Output = CovariantIndex; 86 | } 87 | 88 | // Back to implementing Variance 89 | 90 | impl Variance for (T, U) 91 | where 92 | U: Variance, 93 | Add1: Unsigned + Add, 94 | T: TensorIndex, 95 | { 96 | type Rank = Add1; 97 | 98 | fn variance() -> Vec { 99 | let mut result = vec![T::index_type()]; 100 | result.append(&mut U::variance()); 101 | result 102 | } 103 | } 104 | 105 | /// Operator trait used for concatenating two variances. 106 | /// 107 | /// Used in tensor outer product. 108 | pub trait Concat: Variance { 109 | type Output: Variance; 110 | } 111 | 112 | /// Helper type for variance concatenation. 113 | pub type Joined = >::Output; 114 | 115 | impl Concat for T 116 | where 117 | T: TensorIndex, 118 | U: TensorIndex, 119 | Add1<::Rank>: Unsigned + Add, 120 | { 121 | type Output = (T, U); 122 | } 123 | 124 | impl Concat for () 125 | where 126 | T: TensorIndex, 127 | { 128 | type Output = T; 129 | } 130 | 131 | impl Concat for (T, U) 132 | where 133 | T: TensorIndex, 134 | V: TensorIndex, 135 | U: Variance + Concat, 136 | >::Output: Variance, 137 | Add1<::Rank>: Unsigned + Add, 138 | Add1< as Variance>::Rank>: Unsigned + Add, 139 | { 140 | type Output = (T, >::Output); 141 | } 142 | 143 | impl Concat<(U, V)> for T 144 | where 145 | T: TensorIndex, 146 | U: TensorIndex, 147 | V: Variance, 148 | Add1<::Rank>: Unsigned + Add, 149 | Add1::Rank>>: Unsigned + Add, 150 | { 151 | type Output = (T, (U, V)); 152 | } 153 | 154 | impl Concat<(V, W)> for (T, U) 155 | where 156 | T: TensorIndex, 157 | U: Variance + Concat<(V, W)>, 158 | V: TensorIndex, 159 | W: Variance, 160 | Add1<::Rank>: Unsigned + Add, 161 | Add1<::Rank>: Unsigned + Add, 162 | Add1< as Variance>::Rank>: Unsigned + Add, 163 | { 164 | type Output = (T, Joined); 165 | } 166 | 167 | /// Indexing operator trait: Output is equal to the index type at the given position 168 | /// 169 | /// Warning: Indices are numbered starting from 0! 170 | pub trait Index: Variance { 171 | type Output: TensorIndex; 172 | } 173 | 174 | /// Helper type for variance indexing. 175 | pub type At = >::Output; 176 | 177 | impl Index for CovariantIndex { 178 | type Output = CovariantIndex; 179 | } 180 | 181 | impl Index for ContravariantIndex { 182 | type Output = ContravariantIndex; 183 | } 184 | 185 | impl Index> for (V, T) 186 | where 187 | V: TensorIndex, 188 | U: Unsigned, 189 | B: Bit, 190 | UInt: Sub, 191 | Sub1>: Unsigned, 192 | T: Variance + Index>>, 193 | Add1<::Rank>: Unsigned + Add, 194 | { 195 | type Output = At>>; 196 | } 197 | 198 | impl Index for (V, T) 199 | where 200 | V: TensorIndex, 201 | T: Variance, 202 | Add1<::Rank>: Unsigned + Add, 203 | { 204 | type Output = V; 205 | } 206 | 207 | /// An operator trait, removing the indicated index from a variance 208 | pub trait RemoveIndex: Variance { 209 | type Output: Variance; 210 | } 211 | 212 | /// Helper type for index removal 213 | pub type Removed = >::Output; 214 | 215 | impl RemoveIndex for CovariantIndex { 216 | type Output = (); 217 | } 218 | 219 | impl RemoveIndex for ContravariantIndex { 220 | type Output = (); 221 | } 222 | 223 | impl RemoveIndex for (U, V) 224 | where 225 | U: TensorIndex, 226 | V: Variance, 227 | Add1<::Rank>: Unsigned + Add, 228 | { 229 | type Output = V; 230 | } 231 | 232 | impl RemoveIndex> for (U, V) 233 | where 234 | T: Unsigned, 235 | B: Bit, 236 | U: TensorIndex, 237 | UInt: Sub, 238 | Sub1>: Unsigned, 239 | V: Variance + RemoveIndex>>, 240 | (U, V): Variance, 241 | (U, Removed>>): Variance, 242 | { 243 | type Output = (U, Removed>>); 244 | } 245 | 246 | /// An operator trait representing tensor contraction 247 | /// 248 | /// Used in tensor inner product 249 | pub trait Contract: Variance { 250 | type Output: Variance; 251 | } 252 | 253 | /// Helper type for contraction 254 | pub type Contracted = >::Output; 255 | 256 | impl Contract for V 257 | where 258 | Ul: Unsigned, 259 | Uh: Unsigned + Sub + Cmp
      , 260 | Sub1: Unsigned, 261 | >::Output: Same, 262 | V: Index
        + Index + RemoveIndex
          , 263 | At: OtherIndex, 264 | At: Same< as OtherIndex>::Output>, 265 | Removed: RemoveIndex>, 266 | Removed, Sub1>: Variance, 267 | { 268 | type Output = Removed, Sub1>; 269 | } 270 | 271 | #[cfg(test)] 272 | mod test { 273 | use super::*; 274 | use crate::typenum::consts::{U0, U1, U2}; 275 | 276 | #[test] 277 | fn test_variance() { 278 | assert_eq!( 279 | <(CovariantIndex, ContravariantIndex) as Variance>::variance(), 280 | vec![IndexType::Covariant, IndexType::Contravariant] 281 | ); 282 | } 283 | 284 | #[test] 285 | fn test_variance_concat() { 286 | assert_eq!( 287 | as Variance>::variance(), 288 | vec![IndexType::Covariant, IndexType::Contravariant] 289 | ); 290 | 291 | assert_eq!( 292 | as Variance>::variance(), 293 | vec![ 294 | IndexType::Covariant, 295 | IndexType::Covariant, 296 | IndexType::Contravariant 297 | ] 298 | ); 299 | 300 | assert_eq!( 301 | as Variance>::variance(), 302 | vec![ 303 | IndexType::Covariant, 304 | IndexType::Covariant, 305 | IndexType::Contravariant 306 | ] 307 | ); 308 | 309 | assert_eq!( as Variance>::variance(), 311 | vec![IndexType::Contravariant, 312 | IndexType::Covariant, 313 | IndexType::Covariant, 314 | IndexType::Contravariant]); 315 | } 316 | 317 | #[test] 318 | fn test_index() { 319 | assert_eq!( 320 | as TensorIndex>::index_type(), 321 | IndexType::Covariant 322 | ); 323 | 324 | assert_eq!( 325 | as TensorIndex>::index_type(), 326 | IndexType::Covariant 327 | ); 328 | 329 | assert_eq!( 330 | as TensorIndex>::index_type(), 331 | IndexType::Contravariant 332 | ); 333 | 334 | assert_eq!( 335 | as TensorIndex> 336 | ::index_type(), 337 | IndexType::Contravariant); 338 | 339 | assert_eq!( 340 | as TensorIndex> 341 | ::index_type(), 342 | IndexType::Covariant); 343 | } 344 | 345 | #[test] 346 | fn test_remove() { 347 | assert_eq!( 348 | as Variance>::variance(), 349 | vec![] 350 | ); 351 | 352 | assert_eq!( 353 | as Variance>::variance(), 354 | vec![IndexType::Contravariant] 355 | ); 356 | 357 | assert_eq!( 358 | as Variance>::variance(), 359 | vec![IndexType::Covariant] 360 | ); 361 | 362 | assert_eq!( 363 | as Variance> 364 | ::variance(), 365 | vec![IndexType::Contravariant, IndexType::Covariant]); 366 | } 367 | 368 | #[test] 369 | fn test_contract() { 370 | assert_eq!( 371 | as Variance>::variance(), 372 | vec![] 373 | ); 374 | 375 | assert_eq!( 376 | as Variance>::variance(), 377 | vec![] 378 | ); 379 | 380 | assert_eq!( 381 | as Variance> 382 | ::variance(), 383 | vec![IndexType::Covariant]); 384 | 385 | assert_eq!( 386 | as Variance> 387 | ::variance(), 388 | vec![IndexType::Covariant]); 389 | } 390 | } 391 | -------------------------------------------------------------------------------- /src/tests/basic.rs: -------------------------------------------------------------------------------- 1 | use crate::coordinates::{CoordinateSystem, Point}; 2 | use crate::tensors::{Covector, InvTwoForm, Matrix, Scalar, TwoForm, Vector}; 3 | use crate::typenum::consts::{U0, U1, U2, U4}; 4 | use crate::{inner, mul}; 5 | use generic_array::arr; 6 | use generic_array::GenericArray; 7 | 8 | struct Test2; 9 | impl CoordinateSystem for Test2 { 10 | type Dimension = U2; 11 | } 12 | 13 | struct Test4; 14 | impl CoordinateSystem for Test4 { 15 | type Dimension = U4; 16 | } 17 | 18 | #[test] 19 | fn test_ranks() { 20 | assert_eq!(Vector::::get_rank(), 1); 21 | assert_eq!(Matrix::::get_rank(), 2); 22 | } 23 | 24 | #[test] 25 | fn test_num_coords() { 26 | assert_eq!(Vector::::get_num_coords(), 4); 27 | assert_eq!(Matrix::::get_num_coords(), 16); 28 | } 29 | 30 | #[test] 31 | fn test_iter_coords() { 32 | let p1 = Point::new(GenericArray::default()); 33 | let matrix1 = Matrix::::zero(p1); 34 | let p2 = Point::new(GenericArray::default()); 35 | let matrix2 = Matrix::::zero(p2); 36 | 37 | let mut i = 0; 38 | for _ in matrix1.iter_coords() { 39 | i += 1; 40 | } 41 | assert_eq!(i, 4); 42 | 43 | i = 0; 44 | for _ in matrix2.iter_coords() { 45 | i += 1; 46 | } 47 | assert_eq!(i, 16); 48 | } 49 | 50 | #[test] 51 | fn test_add() { 52 | let p = Point::new(GenericArray::default()); 53 | let vector1 = Vector::::new(p, arr![f64; 1.0, 2.0]); 54 | let vector2 = Vector::::new(p, arr![f64; 1.5, 1.6]); 55 | 56 | let result = vector1 + vector2; 57 | 58 | assert_eq!(result[0], 2.5); 59 | assert_eq!(result[1], 3.6); 60 | } 61 | 62 | #[test] 63 | fn test_sub() { 64 | let p = Point::new(GenericArray::default()); 65 | let vector1 = Vector::::new(p, arr![f64; 1.0, 2.0]); 66 | let vector2 = Vector::::new(p, arr![f64; 1.5, 1.75]); 67 | 68 | let result = vector1 - vector2; 69 | 70 | assert_eq!(result[0], -0.5); 71 | assert_eq!(result[1], 0.25); 72 | } 73 | 74 | #[test] 75 | fn test_trace() { 76 | let p = Point::new(GenericArray::default()); 77 | let matrix = Matrix::::new(p, arr![f64; 1.0, 3.0, 0.0, 3.0]); 78 | 79 | let tr = matrix.trace::(); 80 | 81 | assert_eq!(*tr, 4.0); 82 | } 83 | 84 | // needed for tests below 85 | 86 | use std::ops::Mul; 87 | 88 | #[test] 89 | fn test_mul_trait() { 90 | assert_eq!( as Mul>>::Output::get_rank(), 2); 91 | assert_eq!( 92 | as Mul>>::Output::get_num_coords(), 93 | 4 94 | ); 95 | assert_eq!( as Mul>::Output::get_rank(), 1); 96 | assert_eq!( as Mul>::Output::get_num_coords(), 2); 97 | } 98 | 99 | #[test] 100 | fn test_mul_scalar() { 101 | let p = Point::new(GenericArray::default()); 102 | let vector1 = Vector::::new(p, arr![f64; 1.0, 2.0]); 103 | // this works 104 | let result: Vector = mul!(_, f64; vector1, 5.0); 105 | // this doesn't 106 | // let result: Vector = vector1 * 5.0; 107 | 108 | assert_eq!(result[0], 5.0); 109 | assert_eq!(result[1], 10.0); 110 | } 111 | 112 | #[test] 113 | fn test_mul_vector() { 114 | let p = Point::new(GenericArray::default()); 115 | let vector1 = Vector::::new(p, arr![f64; 1.0, 2.0]); 116 | let vector2 = Vector::::new(p, arr![f64; 3.0, 4.0]); 117 | 118 | // this works 119 | let result: InvTwoForm = mul!(_, Vector; vector1, vector2); 120 | // this doesn't 121 | // let result = vector1 * vector2; 122 | 123 | assert_eq!(result[0], 3.0); 124 | assert_eq!(result[1], 4.0); 125 | assert_eq!(result[2], 6.0); 126 | assert_eq!(result[3], 8.0); 127 | } 128 | 129 | #[test] 130 | fn test_inner_product() { 131 | let p = Point::new(GenericArray::default()); 132 | let vector1 = Vector::::new(p, arr![f64; 1.0, 2.0]); 133 | let vector2 = Covector::::new(p, arr![f64; 3.0, 4.0]); 134 | 135 | let result: Scalar = inner!(_, Covector; U0, U1; vector1, vector2); 136 | 137 | assert_eq!(*result, 11.0); 138 | } 139 | 140 | #[test] 141 | fn test_complex_inner_product() { 142 | let p = Point::new(GenericArray::default()); 143 | let form = TwoForm::::new(p, arr![f64; 1.0, 0.0, 0.0, 1.0]); 144 | let vector1 = Vector::::new(p, arr![f64; 1.0, 2.0]); 145 | let vector2 = Vector::::new(p, arr![f64; 3.0, 4.0]); 146 | 147 | let temp = inner!(_, Vector; U0, U2; form, vector1); 148 | let result = inner!(_, Vector; U0, U1; temp, vector2); 149 | 150 | assert_eq!(*result, 11.0); 151 | } 152 | 153 | #[test] 154 | fn test_transpose() { 155 | let p = Point::new(GenericArray::default()); 156 | let matrix = Matrix::::new(p, arr![f64; 1.0, 2.0, 3.0, 4.0]); 157 | 158 | let result = matrix.transpose(); 159 | 160 | assert_eq!(result[0], 1.0); 161 | assert_eq!(result[1], 3.0); 162 | assert_eq!(result[2], 2.0); 163 | assert_eq!(result[3], 4.0); 164 | } 165 | 166 | #[test] 167 | fn test_inverse() { 168 | let p = Point::new(GenericArray::default()); 169 | let matrix = Matrix::::new(p, arr![f64; 1.0, 2.0, 3.0, 4.0]); 170 | 171 | let result = matrix.inverse().unwrap(); 172 | 173 | // unfortunately the inverse matrix calculation isn't perfectly accurate 174 | let epsilon = 0.0000001; 175 | 176 | assert!((result[0] + 2.0).abs() < epsilon); 177 | assert!((result[1] - 1.0).abs() < epsilon); 178 | assert!((result[2] - 1.5).abs() < epsilon); 179 | assert!((result[3] + 0.5).abs() < epsilon); 180 | } 181 | -------------------------------------------------------------------------------- /src/tests/coord_transform.rs: -------------------------------------------------------------------------------- 1 | use crate::coordinates::{ConversionTo, CoordinateSystem, Point}; 2 | use crate::tensors::Vector; 3 | use crate::typenum::consts::U3; 4 | use generic_array::arr; 5 | 6 | struct Cartesian; 7 | struct Spherical; 8 | 9 | impl CoordinateSystem for Cartesian { 10 | type Dimension = U3; 11 | } 12 | 13 | impl CoordinateSystem for Spherical { 14 | type Dimension = U3; 15 | } 16 | 17 | impl ConversionTo for Cartesian { 18 | fn convert_point(p: &Point) -> Point { 19 | let r = (p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt(); 20 | let theta = (p[2] / r).acos(); 21 | let phi = p[1].atan2(p[0]); 22 | Point::new(arr![f64; r, theta, phi]) 23 | } 24 | } 25 | 26 | #[test] 27 | fn test_vector_to_spherical() { 28 | let p = Point::new(arr![f64; 0.0, 1.0, 1.0]); 29 | let v = Vector::::new(p, arr![f64; 0.0, 0.0, 1.0]); 30 | let v2: Vector = v.convert(); 31 | assert!((v2[0] - 0.5_f64.sqrt()).abs() < 0.00001); 32 | assert!((v2[1] + 0.5).abs() < 0.00001); 33 | assert_eq!(v2[2], 0.0); 34 | } 35 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod basic; 2 | mod coord_transform; 3 | --------------------------------------------------------------------------------