All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tensor.Tensor.scala Maven / Gradle / Ivy

package galileo.tensor

import galileo.environment.Environment
import galileo.expr._
import galileo.linalg.{DenseMatrix,Matrix}
import galileo.selectable.Selectable

object TensorIndexKind extends Enumeration {
	type TensorIndexKind = Value
	val Upper, Lower = Value
}

import TensorIndexKind._

case class TensorIndex( kind:TensorIndexKind, dimension:Int) {
	def inverse = kind match {
		case Upper => TensorIndex( Lower, dimension )
		case Lower => TensorIndex( Upper, dimension )
	}
	//def variables = 
}

// to be renamed Tensor after Tensor is renamed DenseTensor
trait TensorTrait {
	def variables:List[Variable] =  {
		throw new IllegalArgumentException( "variables should not be called on a Tensor" )
		List()
	}
}

object Tensor {
	// All indices are assumed to be of the same dimension
	def apply(kinds:List[TensorIndexKind],dimension:Int,components:List[Expr]):Tensor = Tensor(
		kinds.map( kind => TensorIndex( kind, dimension ) ),
		components
	)

	def metricInverse(metric:Tensor):Tensor = {
		val indices = metric.indices.map( index => index.inverse )
		val m = metric.toMatrix.inverse.toDenseMatrix.rows.flatten
		Tensor( indices, m )
	}

	def transformationMatrix( unprimed:List[Expr], primed:List[Expr]):Tensor = { 
		require( primed.size == unprimed.size )
		//var jacobian:List[Expr] = List()
		var jacobian = primed.map( primedE => unprimed.map( unprimedE => Derivative( unprimedE, primedE ).visit() ) ).flatten

		Tensor(
			List( Upper, Lower ),
			primed.size,
			jacobian
		)
	}
}

case class TensorRank(upper:Int,lower:Int){
	def toInt:Int = upper + lower
	def +(that:TensorRank) = TensorRank( this.upper + that.upper, this.lower + that.lower )
}

// Should really extend TensorTrait .. this should be called DenseTensor
case class Tensor( indices:List[TensorIndex], components:List[Expr]) extends Expr with Selectable {
	def rank:TensorRank = TensorRank( indices.filter( x => x.kind == Upper ).size, indices.filter( x => x.kind == Lower ).size )
	lazy val rankInt = rank.toInt
	def info(env:Option[Environment]=None) = "Tensor(" + indices + "," + components + ")"
	def toMatrix:Matrix = {
		require( indices.size == 2 )
		DenseMatrix( components.grouped(indices(1).dimension ).toList )
	}

	def valueAt( location:Int* ):Expr = this.valueAt( location.to[List] )
	private def valueAt( location:List[Int]):Expr = {
		require( location.size == rankInt )
		for( i <- 0 until rankInt ) {
			if( location( i ) < 0 || location( i ) >= indices(i).dimension )
				throw new IndexOutOfBoundsException( "location(" + i + ") out of bounds [0," + indices(i).dimension + "]" )
		}

		components( address(location ) )
	}

	def swapIndices( left:Int,right:Int) = { 
		require( 0 <= left )
		require( left < this.rankInt )
		require( 0 <= right )
		require( right < this.rankInt )

		val swappedIndices = this.indices.updated(left,this.indices(right)).updated(right,this.indices(left))
		//val allAddresses = ( 0 until this.totalSize )
		var swappedComponents:List[Expr] = List()
		for( i <- 0 until this.totalSize ) {
			var loc = this.location( i )
			loc = loc.updated( left, loc( right ) ).updated( right, loc( left ) )
			val comp = this.components( this.address( loc ) )
			swappedComponents = swappedComponents :+ comp
		}
		Tensor( swappedIndices, swappedComponents )
	}
	// Reduced rank tensor, extract with a specific value of index
	def valuesAtIndex(index:Int,value:Int):Tensor = {
		require( index < this.rankInt )
		require( value >= 0 )
		require( value < indices(index).dimension )
		Tensor( indices.patch( index, Nil, 1 ), locations( index, value ).map( location => this.valueAt( location ) ).to[List] )
	}

	// all locations for all indices associated with index set to value
	// e.g. index = 1, value = 2: Find all locations (:,2,:,:....)
	private def locations( index:Int,value:Int) = {
		val allAddresses = ( 0 until this.totalSize )
		allAddresses.map( address => this.location( address ) ).filter( location => location( index ) == value )
	}

	def location( address:Int ):List[Int] = {
		val offsets = indices.map( index => index.dimension ).scanRight( 1 )(_*_).slice( 1, rankInt+1 /* any big number would do */)
		var rem = address
		offsets.foldLeft(List[Int]()){ case ( l,e ) => { val q = rem / e; rem -= q * e; l :+ q } }
	}

	def totalSize = indices.map( index => index.dimension ).foldLeft(1)(_*_)

	// Let's say we have the following dimensions:
	// 4, 3, 5
	// location     address
	// 0,0,0 	->   0
	// 0,0,1    ->   1
	// 0,0,4    ->   4
	// 0,1,0,   ->   5
	// 0,1,4    ->   9
	// 0,2,0    ->  10
	// 0,2,4    ->  14
	// 1,0,0    ->  15
	// 2,0,0    ->  30
	// 3,0,0    ->  45
	// 3,2,4    ->  59 (last address)
	// offsets, in this case would be 15,5,1
	private def address( location:List[Int] ):Int = {
		val offsets = indices.map( index => index.dimension ).scanRight( 1 )(_*_).slice( 1, rankInt+1 /* any big number would do */)
		require( offsets.size == location.size )
		location.zip( offsets ).map( { case (l,o) => l * o } ).sum
	}

	//def *(that:Expr) = Tensor( this.indices, this.components.map( component => Product( component, that ).visit() ) )
	// element wise division
	def /(that:Expr) = Tensor( this.indices, this.components.map( component => Fraction( component, that ).visit() ) )

	// Use this to reduce rank,
	// E.g Aj_l <- contract Ajk_kl on k, upperIndex=1, lowerIndex=2 (indices go like upper j=0, upper k=1, lower k=2, lower l=3)
	// Mechanically this means
	// Aj_l = Aj0_0l+Aj1_1l+Aj2_2l...
	def contract( upperIndex:Int, lowerIndex:Int ):Tensor = {
		require( this.rank.upper > 0 )
		require( this.rank.lower > 0 )
		require( upperIndex < this.rankInt )
		require( lowerIndex < this.rankInt )
		require( this.indices( upperIndex ).kind == Upper )
		require( this.indices( lowerIndex ).kind == Lower )
		require( this.indices( upperIndex ).dimension == this.indices( lowerIndex ).dimension )

		// remove the lower and upper indices
		val before = this.indices.size
		val productIndices = this.indices.zipWithIndex.filter( { case (_,_2) => _2 != upperIndex && _2 != lowerIndex } ).map( _._1 )
		val after = productIndices.size
		require( before == after + 2 )
		val productSize = productIndices.map( index => index.dimension ).foldLeft(1)(_*_)
		var rv:Tensor = Tensor( productIndices,  List.fill( productSize )( Number( 0 ) ) )
		
		val n = this.indices( upperIndex ).dimension
		for( i <- 0 until n ) { // do we need to change order if upperIndex > lowerIndex?
			if( upperIndex < lowerIndex )
				rv = rv + ( this.valuesAtIndex( upperIndex, i ).valuesAtIndex( lowerIndex - 1, i ) ) //.visit()
			else
				rv = rv + ( this.valuesAtIndex( lowerIndex, i ).valuesAtIndex( upperIndex - 1, i ) ) //.visit()
		}
		rv
	}

	// Tensor + Expr
	override def +(that:Expr) = that match {
		case t:Tensor => this tensorPlus t
		case e:Expr => Tensor( this.indices, this.components.map( component => Sum( component, that ).visit() ) )
	}
	def +(that:Tensor):Tensor = this tensorPlus that 

	// Tensor + Tensor
	private def tensorPlus(that:Tensor):Tensor = that.indices match {
		case Nil => { 
			require( that.components.size == 1 )
			Tensor( this.indices, components.map( component => Sum( component, that.components( 0 ) ).visit() ) )
		}
		case l:List[TensorIndex] => {
			require( this.indices == l, "l:\n" + l + "\nthis.indices\n" + this.indices )
			Tensor( this.indices, this.components.zip( that.components ).map( { case ( _1:Expr,_2:Expr) => Sum(_1,_2).visit() } ) )
		}
	}

	// Tensor - Tensor
	def -(that:Tensor) = that.indices match {
		case Nil => { 
			require( that.components.size == 1 )
			Tensor( this.indices, components.map( component => Sum( component, Product( Number( -1 ), that.components( 0 ) ) ).visit() ) )
		}
		case l:List[TensorIndex] => {
			require( this.indices == l, "l:\n" + l + "\nthis.indices\n" + this.indices )
			Tensor( this.indices, this.components.zip( that.components ).map( { case ( _1:Expr,_2:Expr) => Sum(_1,Product(Number(-1),_2)).visit() } ) )
		}
	}

	// Tensor * Expr
	def *(that:Expr):Tensor = that match {
		case t:Tensor => this tensorTimes t
		case e:Expr => Tensor( this.indices, this.components.map( component => Product( component, that ).visit() ) )
	}

	// Tensor * Tensor
	def tensorTimes(that:Tensor) = Tensor( 
		this.indices ++ that.indices,
		this.components.map( thiscomponent => that.components.map( thatcomponent => Product( thiscomponent, thatcomponent ).visit() ) ).flatten
	)

	override def visit(env:Option[Environment]=None) = Tensor( indices, components.map( component => component.visit( env ) ) )
	override def expand = Tensor( indices, components.map( component => component.expand ) )
	override def simplify = Tensor( indices, components.map( component => component.simplify ) )
	override def factor = Tensor( indices, components.map( component => component.factor ) )

	// todo - clearly :)
	def select(indices:List[Expr]):Expr = {
		Number( 3 )
	}

	def variables:List[Variable] = components.flatMap( component => component.variables )
}

trait TensorU extends Expr with Statement {
	def info(env:Option[Environment]=None) = this.getClass.getSimpleName + "(" + expr + ")"
	val expr:Expr
	val generator:Metric=>Tensor
	override def visit(env:Option[Environment]=None) = expr.visit( env ) match {
		case metric:Metric => generator( metric )
		case a => this //.apply(a)
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy