//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** @author John Miller, Hao Peng * @version 1.6 * @date Thu May 10 15:50:15 EDT 2018 * @see LICENSE (MIT style license file). * * Tensor Algebra * @see www.stat.uchicago.edu/~lekheng/work/icm1.pdf * @see www.math.ias.edu/csdm/files/13-14/Gnang_Pa_Fi_2014.pdf * @see tspace.l */ package scalation.tenalgebra import scala.runtime.ScalaRunTime.stringOf import scalation.linalgebra.{MatriD, MatrixD, VectoD, VectorD} import scalation.util.{banner, Error} //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `Tensor3D` class is a simple implementation for 3-dimensional tensors. * The names of the dimensions corresponds to MATLAB (row, column, sheet). * @param dim1 size of the 1st level/dimension (row) of the tensor (height) * @param dim2 size of the 2nd level/dimension (column) of the tensor (width) * @param dim3 size of the 3rd level/dimension (sheet) of the tensor (depth) */ class Tensor3D (val dim1: Int, val dim2: Int, val dim3: Int, private [tenalgebra] var v: Array [Array [Array [Double]]] = null) extends Error with Serializable { private val DEBUG = true // debug flag private val TAB = "\t\t" // use "\t" for scala and "\t\t" for sbt val range1 = 0 until dim1 // range for the first level/dimension val range2 = 0 until dim2 // range for the second level/dimension val range3 = 0 until dim3 // range for the third level/dimension /** Multi-dimensional array storage for tensor */ if (v == null) { v = Array.ofDim [Double] (dim1, dim2, dim3) } else if (dim1 != v.length || dim2 != v(0).length || dim3 != v(0)(0).length) { flaw ("constructor", "dimensions are wrong") } // if /** Format string used for printing vector values (change using 'setFormat') */ protected var fString = "%g,\t" //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Construct a 'dim1' by 'dim1' by 'dim1' cubic tensor. * @param dim1 the row and column dimension */ def this (dim1: Int) = { this (dim1, dim1, dim1) } //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Construct a tensor from three dimensional array. * @param u the three dimensional array */ def this (u: Array [Array [Array [Double]]]) = { this (u.size, u(0).size, u(0)(0).size, u) } //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Set the format to the 'newFormat'. * @param newFormat the new format string */ def setFormat (newFormat: String): Unit = { fString = newFormat } //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Retrieve the 'i, j, k' element from the tensor. * @param i 1st dimension (row) index of the tensor * @param j 2nd dimension (column) index of the tensor * @param k 3rd dimension (sheet) index of the tensor */ def apply (i: Int, j: Int, k: Int): Double = v(i)(j)(k) //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Retrieve the 'i, j' vector from the tensor. * @param i 1st dimension (row) index of the tensor * @param j 2nd dimension (column) index of the tensor */ def apply (i: Int, j: Int): VectoD = VectorD (v(i)(j).toIndexedSeq) //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Retrieve the 'i' matrix from the tensor. * @param i 1st dimension (row) index of the tensor */ def apply (i: Int): MatriD = MatrixD (v(i)) //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Retrieve the 'ii._1' to 'ii._2' row slice of the tensor. * @param ii 1st dimension (row) indices of the tensor */ def apply (ii: Int2): Tensor3D = new Tensor3D (v.slice (ii._1, ii._2)) //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Retrieve the 'ii._1' to 'ii._2', 'jj._1' to 'jj._2' row-column slice of the tensor. * @param ii 1st dimension (row) indices of the tensor (null => all) * @param jj 2nd dimension (column) indices of the tensor */ def apply (ii: Int2, jj: Int2): Tensor3D = { val (i1, i2) = if (ii == null) (0, dim1) else ii val u = v.slice (i1, i2) for (i <- u.indices) u(i) = u(i).slice (jj._1, jj._2) new Tensor3D (u) } // apply //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Retrieve the 'ii._1' to 'ii._2', 'jj._1' to 'jj._2', 'kk._1' to 'kk._2' * row-column-sheet slice of the tensor. * @param ii 1st dimension (row) indices of the tensor (null => all) * @param jj 2nd dimension (column) indices of the tensor (null => all) * @param kk 3rd dimension (sheet) indices of the tensor */ def apply (ii: Int2, jj: Int2, kk: Int2): Tensor3D = { val (i1, i2) = if (ii == null) (0, dim1) else ii val (j1, j2) = if (jj == null) (0, dim2) else jj val u = v.slice (i1, i2) for (i <- u.indices) u(i) = u(i).slice (j1, j2) for (i <- u.indices; j <- u(i).indices) u(i)(j) = u(i)(j).slice (kk._1, kk._2) new Tensor3D (u) } // apply //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Retrieve the 'is' row selections from the tensor. * @param is 1st dimension (row) indices of the tensor */ def apply (is: Ints): Tensor3D = { val u = Array.ofDim [Double] (is.size, dim2, dim3) for (i <- is.indices; j <- range2; k <- range3) u(i)(j)(k) = v(is(i))(j)(k) new Tensor3D (u) } // apply //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Retrieve the 'is, js' row-column selections from the tensor. * @param is 1st dimension (row) indices of the tensor (null => all) * @param js 2nd dimension (column) indices of the tensor */ def apply (is: Ints, js: Ints): Tensor3D = { if (is == null) { val u = Array.ofDim [Double] (dim1, js.size, dim3) for (i <- range1; j <- js.indices; k <- range3) u(i)(j)(k) = v(i)(js(j))(k) new Tensor3D (u) } else { val u = Array.ofDim [Double] (is.size, js.size, dim3) for (i <- is.indices; j <- js.indices; k <- range3) u(i)(j)(k) = v(is(i))(js(j))(k) new Tensor3D (u) } // if } // apply //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Retrieve the 'is, js, ks' row-column-sheet selections from the tensor. * @param is 1st dimension (row) indices of the tensor (null => all) * @param js 2nd dimension (column) indices of the tensor (null => all) * @param ks 3rd dimension (sheet) indices of the tensor */ def apply (is: Ints, js: Ints, ks: Ints): Tensor3D = { if (is == null && js == null) { val u = Array.ofDim [Double] (dim1, dim2, ks.size) for (i <- range1; j <- range2; k <- ks.indices) u(i)(j)(k) = v(i)(j)(ks(k)) new Tensor3D (u) } else if (is == null) { val u = Array.ofDim [Double] (dim1, js.size, ks.size) for (i <- range1; j <- js.indices; k <- ks.indices) u(i)(j)(k) = v(i)(js(j))(ks(k)) new Tensor3D (u) } else if (js == null) { val u = Array.ofDim [Double] (is.size, dim2, ks.size) for (i <- is.indices; j <- range2; k <- ks.indices) u(i)(j)(k) = v(is(i))(j)(ks(k)) new Tensor3D (u) } else { val u = Array.ofDim [Double] (is.size, js.size, ks.size) for (i <- is.indices; j <- js.indices; k <- ks.indices) u(i)(j)(k) = v(is(i))(js(j))(ks(k)) new Tensor3D (u) } // if } // apply //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Retrieve the complement of the 'is' row selections from the tensor. * @param is 1st dimension (row) indices of the tensor */ def not (is: Ints): Tensor3D = apply (Array.range (0, dim1) diff is) //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Retrieve the complement of the 'is' row selections from the tensor. * @param is 1st dimension (row) indices of the tensor * @param js 2nd dimension (column) indices of the tensor */ def not (is: Ints, js: Ints): Tensor3D = apply (comple (is, dim1), comple (js, dim2)) //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Retrieve the complement of the 'is' row selections from the tensor. * @param is 1st dimension (row) indices of the tensor * @param js 2nd dimension (column) indices of the tensor * @param ks 3rd dimension (sheet) indices of the tensor */ def not (is: Ints, js: Ints, ks: Ints): Tensor3D = { apply (comple (is, dim1), comple (js, dim2), comple (ks, dim3)) } // not //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Update a single element of the tensor to the given value. * @param i 1st dimension (row) index of the tensor * @param j 2nd dimension (column) index of the tensor * @param k 3rd dimension (sheet) index of the tensor * @param x the value to be updated at the above position in the tensor */ def update (i: Int, j: Int, k: Int, x: Double): Unit = v(i)(j)(k) = x //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Update a single vector of the tensor to the given vector. * @param i 1st dimension (row) index of the tensor * @param j 2nd dimension (column) index of the tensor * @param x the vector to be updated at the above position in the tensor */ def update (i: Int, j: Int, x: VectorD): Unit = v(i)(j) = x().toArray //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Update a single matrix of the tensor to the given matrix. * @param i 1st dimension (row) index of the tensor * @param x the matrix to be updated at the above position in the tensor */ def update (i: Int, x: MatrixD): Unit = v(i) = x().toArray //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Set all the tensor element values to 'x'. * @param x the value to set all elements to */ def set (x: Double): Unit = { for (i <- range1; j <- range2; k <- range3) v(i)(j)(k) = x } //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Add 'this' tensor and tensor 'b'. * @param b the tensor to add (requires 'leDimensions') */ def + (b: Tensor3D): Tensor3D = { val c = new Tensor3D (dim1, dim2, dim3) for (i <- range1; j <- range2; k <- range3) { c.v(i)(j)(k) = v(i)(j)(k) + b.v(i)(j)(k) } // for c } // + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Add 'this' tensor and scalar 's'. * @param s the scalar to add */ def + (s: Double): Tensor3D = { val c = new Tensor3D (dim1, dim2, dim3) for (i <- range1; j <- range2; k <- range3) { c.v(i)(j)(k) = v(i)(j)(k) + s } // for c } // + //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** From 'this' tensor subtract tensor 'b'. * @param b the tensor to add (requires 'leDimensions') */ def - (b: Tensor3D): Tensor3D = { val c = new Tensor3D (dim1, dim2, dim3) for (i <- range1; j <- range2; k <- range3) { c.v(i)(j)(k) = v(i)(j)(k) - b.v(i)(j)(k) } // for c } // - //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** From 'this' tensor subtract scalar 's'. * @param s the scalar to add */ def - (s: Double): Tensor3D = { val c = new Tensor3D (dim1, dim2, dim3) for (i <- range1; j <- range2; k <- range3) { c.v(i)(j)(k) = v(i)(j)(k) - s } // for c } // - //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Multiply 'this' tensor by scalar 's'. * @param s the scalar to multiply by */ def * (s: Double): Tensor3D = { val c = new Tensor3D (dim1, dim2, dim3) for (i <- range1; j <- range2; k <- range3) { c.v(i)(j)(k) = v(i)(j)(k) * s } // for c } // * //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Multiply (multilinear product) 'this' tensor by three matrices 'b', 'c' and 'd'. *

* this * (a, b, c) *

* @see www.stat.uchicago.edu/~lekheng/work/icm1.pdf - equation 15.1 * @param b the first matrix to multiply by (requires 'leDimensions') * @param c the second matrix to multiply by (requires 'leDimensions') * @param d the thrid matrix to multiply by (requires 'leDimensions') */ def * (b: MatrixD, c: MatrixD, d: MatrixD): Tensor3D = { val (m1, n1) = (b.dim1, b.dim2) val (m2, n2) = (c.dim1, c.dim2) val (m3, n3) = (d.dim1, d.dim2) if (n1 > dim2 || n2 > dim2 || n3 > dim3) flaw ("*", "dimensions don't match") val e = new Tensor3D (m1, m2, m3) for (i <-b.range1; j <- c.range1; k <- d.range1) { var sum = 0.0 for (l1 <- b.range2; l2 <- c.range2; l3 <- d.range2) { sum += b(i, l1) * c(j, l2) * d(k, l3) * v(l1)(l2)(l3) } // for e.v(i)(j)(k) = sum } // for e } // * //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Multiply elementwise (Hadamard product) 'this' tensor by tensor 'b'. * @param b the tensor to add (requires 'leDimensions') */ def ** (b: Tensor3D): Tensor3D = { val c = new Tensor3D (dim1, dim2, dim3) for (i <- range1; j <- range2; k <- range3) { c.v(i)(j)(k) = v(i)(j)(k) * b.v(i)(j)(k) } // for c } // ** //:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Check whether the dimensions of 'this' tensor are less than or equal to * 'le' those of the other tensor 'b'. * @param b the other matrix */ def leDimensions (b: Tensor3D): Boolean = { dim1 <= b.dim1 && dim2 <= b.dim2 && dim3 <= b.dim3 } // leDimensions //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Convert 'this' tensor to a string with a double line break after each sheet * and a single line break after each row. */ override def toString: String = { val sb = new StringBuilder ("\nTensor3D(") if (dim1 == 0) return sb.append (")").mkString for (k <- range3) { for (i <- range1) { for (j <- range2) sb.append (s"${v(i)(j)(k)}, ") sb.append ("\n" + TAB) } // for sb.append ("\n" + TAB) } // for sb.replace (sb.length-5, sb.length, ")").mkString } // toString //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Convert 'this' tensor to a string with a line break after each sheet. */ def toString2: String = { val sb = new StringBuilder ("\nTensor3D( ") if (dim1 == 0) return sb.append (")").mkString for (i <- range1) { for (j <- range2) { sb.append (stringOf (v(i)(j)) + ", ") if (j == dim2-1) sb.replace (sb.length-1, sb.length, "\n\t") } // for } // for sb.replace (sb.length-3, sb.length, ")").mkString } // toString2 } // Tensor3D class //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `Tensor3D` companion object provides factory methods for the `Tensor3D` class. */ object Tensor3D { //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** Build a tensor from the argument list 'x'. * @param n1 the first dimension * @param n2 the second dimension * @param n3 the third dimension */ def apply (n: (Int, Int, Int), x: Double*): Tensor3D = { val t = new Tensor3D (n._1, n._2, n._3) var l = 0 for (k <- 0 until n._3; i <- 0 until n._1; j <- 0 until n._2) { t(i, j, k) = x(l) l += 1 } // for t } // apply } // Tensor3D object //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `Tensor3DTest` object is used to test the `Tensor3D` class. * > runMain scalation.tenalgebra.Tensor3DTest */ object Tensor3DTest extends App { val s = 2.0 val a = new Tensor3D (2, 3, 2) val b = new Tensor3D (2, 3, 2) // row column sheet val c = Tensor3D ((2, 3, 2), 1, 2, 3, // 0 0-2 0 4, 5, 6, // 1 0-2 0 7, 8, 9, // 0 0-2 1 10, 11, 12) // 1 0-2 1 for (i <- 0 until 2; j <- 0 until 3; k <- 0 until 2) { val sum = i + j + k a(i, j, k) = sum b(i, j, k) = sum } // for println ("s = " + s) println ("a = " + a) println ("b = " + b) println ("c = " + c) println ("c(0) = " + c(0)) println ("c(0, 0) = " + c(0, 0)) println ("c(0, 0, 0) = " + c(0, 0, 0)) banner ("Test operators") println ("a + b = " + (a + b)) println ("a + s = " + (a + b)) println ("a - b = " + (a - b)) println ("a - s = " + (a - s)) println ("c * s = " + c * s) println ("a ** c = " + a ** c) val x = MatrixD ((2, 2), 1, 2, 3, 4) val y = MatrixD ((2, 3), 1, 2, 3, 4, 5, 6) val z = MatrixD ((2, 2), 5, 6, 7, 8) println ("c * (x, y, z) = " + c * (x, y, z)) banner ("Test slice") println ("c = " + c) println ("slice row 0:1 = " + c((0, 1))) println ("slice row col: 0:1, 0:2 = " + c((0, 1), (0, 2))) println ("slice col: null, 0:2 = " + c(null, (0, 2))) println ("slice row col sheet: 0:1, 0:2, 0:1 = " + c((0, 1), (0, 2), (0, 1))) println ("slice sheet: null, null, 0:1 = " + c(null, null, (0, 1))) println ("slice row sheet: 0:1, null, 0:1 = " + c((0, 1), null, (0, 1))) println ("slice col sheet null, 0:2, 0:1 = " + c(null, (0, 2), (0, 1))) banner ("Test select") println ("c = " + c) println ("select row 0 = " + c(Ints (0))) println ("select row col: 0, 0,2 = " + c(Ints (0), Ints (0, 2))) println ("select col: null, 0,2 = " + c(null, Ints (0, 2))) println ("select row col sheet: 0, 0,2, 1 = " + c(Ints (0), Ints (0, 2), Ints (1))) println ("select sheet: null, null, 1 = " + c(null, null, Ints (1))) println ("select row sheet: 0, null, 1 = " + c(Ints (0), null, Ints (1))) println ("select col sheet null, 0,2, 1 = " + c(null, Ints (0, 2), Ints (1))) banner ("Test not") println ("c = " + c) println ("not row 0 = " + c.not(Ints (0))) println ("not row col: 0, 0,2 = " + c.not(Ints (0), Ints (0, 2))) println ("not row col sheet: 0, 0,2, 1 = " + c.not(Ints (0), Ints (0, 2), Ints (1))) } // Tensor3DTest object //::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: /** The `Tensor3DTest2` object is used to test the `Tensor3D` class. * It tests the use of tensors and matrices for convolutional operation needed in * Convolutional Nets. * > runMain scalation.tenalgebra.Tensor3DTest2 */ object Tensor3DTest2 extends App { val a = new Tensor3D (2, 9, 9) for (i <- a.range1; j <- a.range2; k <- a.range3) a(i, j, k) = i + j + k println (s"a = $a") val image0 = a(0) val image1 = a(1) println (s"image0 = $image0") println (s"image1 = $image1") val kernel = new MatrixD ((3, 3), 1, 2, 1, 2, 3, 2, 1, 2, 1) println (s"kernel = $kernel") val sp = new MatrixD (image0.dim1 - kernel.dim2 + 1, image0.dim2 - kernel.dim2 + 1) // for (i <- sp.range1; j <- sp.range2) sp(i, j) = kernel **+ (image0, i, j) // FIX **+ only in MatrixD.scala.sav println (s"sp = $sp") } // Tensor3DTest2 object