From a70a67a87ef3135cee589fb5ac4ee4b8c67124b8 Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Sun, 29 Sep 2024 11:21:41 +0200 Subject: [PATCH] massive refactor packages --- .mill-version | 2 +- .../_blog/_posts/2024-08-01-Motivation.md | 2 +- site/docs/_docs/bounds.check.md | 12 +-- site/docs/_docs/examples.mdoc.md | 16 +-- site/docs/_docs/index.md | 4 +- site/docs/_docs/js.mdoc.md | 3 +- site/docs/_docs/matrix.mdoc.md | 34 +++---- site/src/fake.scala | 5 + vecxt/js/src/array.scala | 7 +- vecxt/js/src/dimCheck.scala | 6 ++ vecxt/js/src/matrix.scala | 71 +++---------- vecxt/jvm-native/src/array.scala | 6 ++ vecxt/jvm-native/src/dimCheck.scala | 5 + vecxt/jvm/src/matrix.scala | 36 +++---- vecxt/native/src/matrix.scala | 99 ++++--------------- vecxt/src/matrixutil.scala | 41 ++++---- vecxt/src/package.scala | 3 + vecxt/test/src/array.test.scala | 9 +- vecxt/test/src/matrix.test.scala | 79 ++++++++++++--- 19 files changed, 208 insertions(+), 232 deletions(-) create mode 100644 site/src/fake.scala create mode 100644 vecxt/jvm-native/src/array.scala diff --git a/.mill-version b/.mill-version index 84d9df3..5746d36 100644 --- a/.mill-version +++ b/.mill-version @@ -1 +1 @@ -0.12.0-RC2 \ No newline at end of file +0.11.12 \ No newline at end of file diff --git a/site/docs/_blog/_posts/2024-08-01-Motivation.md b/site/docs/_blog/_posts/2024-08-01-Motivation.md index 1a501e5..fabf716 100644 --- a/site/docs/_blog/_posts/2024-08-01-Motivation.md +++ b/site/docs/_blog/_posts/2024-08-01-Motivation.md @@ -21,7 +21,7 @@ For example if your data acquisition is serverside, but do parts of a calculatio ```scala mdoc -import vecxt.* +import vecxt.all.* import narr.* import vecxt.BoundsCheck.DoBoundsCheck.yes diff --git a/site/docs/_docs/bounds.check.md b/site/docs/_docs/bounds.check.md index d00b09b..76fc611 100644 --- a/site/docs/_docs/bounds.check.md +++ b/site/docs/_docs/bounds.check.md @@ -16,8 +16,8 @@ It is left to the developer, to decide whether, or where BoundsChecks are desira In this case, we disable bounds checks, maximising performance. ```scala -import vecxt._ -import BoundsCheck.DoBoundsCheck.no +import vecxt.all.* +import vecxt.BoundsCheck.DoBoundsCheck.no val v1 = Array[Double](1, 2, 3) val v2 = Array[Double](4, 5, 6) @@ -29,8 +29,8 @@ println(v1 + v2) In this case, we disable bounds checks, maximising performance, and generate undefined runtime behaviour. It may fail, it may not, but the results will be unpredictable, wrong and potentially hard to track - it is _your_ responsibility. ```scala -import vecxt._ -import BoundsCheck.DoBoundsCheck.no +import vecxt.all.* +import vecxt.BoundsCheck.DoBoundsCheck.no val v1 = Array[Double](1, 2, 3, 7) val v2 = Array[Double](4, 5, 6) @@ -58,11 +58,11 @@ Finally, one may opt in, or out at any individual callsite, should it be desirab ```scala import vecxt.all.* -import BoundsCheck.DoBoundsCheck.no +import vecxt.BoundsCheck.DoBoundsCheck.no val v1 = Array[Double](1, 2, 3, 7) val v2 = Array[Double](4, 5, 6) -println(v1.+(v2)(using BoundsCheck.DoBoundsCheck.yes) ) +println(v1.+(v2)(using vecxt.BoundsCheck.DoBoundsCheck.yes) ) ``` \ No newline at end of file diff --git a/site/docs/_docs/examples.mdoc.md b/site/docs/_docs/examples.mdoc.md index ee36d9c..5e05b64 100644 --- a/site/docs/_docs/examples.mdoc.md +++ b/site/docs/_docs/examples.mdoc.md @@ -16,18 +16,18 @@ v1.dot(v2) cosineSimilarity(v1, v2) -(v1 + v2).print +(v1 + v2).printArr -(v1 - v2).print +(v1 - v2).printArr -(v1 * 2.0).print +(v1 * 2.0).printArr -(v1 / 2.0).print +(v1 / 2.0).printArr -(v1 > 2).print -(v1 >= 2).print +(v1 > 2).printArr +(v1 >= 2).printArr -(v1 < 2).print -(v1 <= 2).print +(v1 < 2).printArr +(v1 <= 2).printArr ``` diff --git a/site/docs/_docs/index.md b/site/docs/_docs/index.md index 7c2e095..612d276 100644 --- a/site/docs/_docs/index.md +++ b/site/docs/_docs/index.md @@ -13,8 +13,8 @@ ivy"io.github.quafadas::vecxt::{{projectVersion}}" ``` ```scala -import vecxt._ -import BoundsCheck.DoBoundsCheck.no +import vecxt.all.* +import vecxt.BoundsCheck.DoBoundsCheck.no val v1 = Array[Double](1, 2, 3) val v2 = Array[Double](4, 5, 6) diff --git a/site/docs/_docs/js.mdoc.md b/site/docs/_docs/js.mdoc.md index 5f98c84..0ff81ff 100644 --- a/site/docs/_docs/js.mdoc.md +++ b/site/docs/_docs/js.mdoc.md @@ -55,8 +55,7 @@ import com.raquo.laminar.DomApi import narr.* -import vecxt.Matrix.* -import vecxt.extensions.* +import vecxt.all.* import vecxt_extensions.MathTagsLaminar.* import vecxt.BoundsCheck.DoBoundsCheck.yes diff --git a/site/docs/_docs/matrix.mdoc.md b/site/docs/_docs/matrix.mdoc.md index a88323d..a75bd0e 100644 --- a/site/docs/_docs/matrix.mdoc.md +++ b/site/docs/_docs/matrix.mdoc.md @@ -22,13 +22,13 @@ val matrix2 = Matrix.fromColumns(nestedArr) matrix.shape -matrix.print +matrix.printMat -matrix2.print +matrix2.printMat -matrix.col(1).print +matrix.col(1).printArr -matrix.row(2).print +matrix.row(2).printArr // Note that indexing is done via a tuple. matrix((1, 2)) @@ -38,29 +38,28 @@ More matrix operations... ```scala mdoc:to-string -import vecxt.Matrix.* +import vecxt.all.* import vecxt.BoundsCheck.DoBoundsCheck.yes import narr.* -import vecxt.extensions.* val mat1 = Matrix(NArray(1.0, 4.0, 2.0, 5.0, 3.0, 6.0), (2, 3)) val mat2 = Matrix(NArray(7.0, 9.0, 11.0, 8.0, 10, 12.0), (3, 2)) val result = mat1.matmul(mat2) -result.print +result.printMat // @ is a reserved character, so we can't just copy numpy syntax... experimental val result2 = mat1 @@ mat2 -result2.print +result2.printMat // opperator precedence... val result3 = Matrix.eye(2) + mat1 @@ mat2 -result3.print +result3.printMat val mat3 = mat2.transpose + mat1 -mat3.print +mat3.printMat ``` @@ -69,10 +68,9 @@ mat3.print Index via a `Int`, `NArray[Int]` or a `Range` to slice a matrix. The `::` operator is used to select all elements in a dimension. ```scala mdoc:to-string -import vecxt.Matrix.* +import vecxt.all.* import vecxt.BoundsCheck.DoBoundsCheck.yes import narr.* -import vecxt.extensions.* val mat = Matrix.fromRows( NArray( @@ -81,12 +79,12 @@ val mat = Matrix.fromRows( NArray[Double](7.0, 8.0, 9.0) ) ) -mat(::, ::).print -mat(1, ::).print -mat(::, 1).print -mat(1, 1).print -mat(0 to 1, 0 to 1).print -mat(NArray.from[Int](Array(0, 2)), 0 to 1).print +mat(::, ::).printMat +mat(1, ::).printMat +mat(::, 1).printMat +mat(1, 1).printMat +mat(0 to 1, 0 to 1).printMat +mat(NArray.from[Int](Array(0, 2)), 0 to 1).printMat ``` \ No newline at end of file diff --git a/site/src/fake.scala b/site/src/fake.scala new file mode 100644 index 0000000..286c903 --- /dev/null +++ b/site/src/fake.scala @@ -0,0 +1,5 @@ +package fake + +object totally: + def s = ??? +end totally diff --git a/vecxt/js/src/array.scala b/vecxt/js/src/array.scala index 9a234b3..b6ad6bb 100644 --- a/vecxt/js/src/array.scala +++ b/vecxt/js/src/array.scala @@ -21,7 +21,12 @@ import scala.util.chaining.* import narr.* import vecxt.BoundsCheck.BoundsCheck +object arrayUtil: +// extension [A](d: Array[A]) def print: String = d.mkString("[", ",", "]") +end arrayUtil + object arrays: + extension (d: Float64Array) def printArr: String = d.mkString("[", ",", "]") extension (v: Float64Array) inline def nativeSort(): Unit = v.asInstanceOf[TypedArrayFacade].sort() @@ -77,7 +82,7 @@ object arrays: // end copy end extension - extension (vec: Float64Array) + extension (vec: NArray[Double]) inline def update(idx: Int, d: Double)(using inline boundsCheck: BoundsCheck.BoundsCheck): Unit = indexCheck(vec, idx) vec(idx) = d diff --git a/vecxt/js/src/dimCheck.scala b/vecxt/js/src/dimCheck.scala index 91acba6..869e19a 100644 --- a/vecxt/js/src/dimCheck.scala +++ b/vecxt/js/src/dimCheck.scala @@ -19,6 +19,12 @@ package vecxt import scala.scalajs.js import scala.scalajs.js.typedarray.Float64Array import vecxt.BoundsCheck.BoundsCheck +import narr.* + +protected[vecxt] object dimCheckLen: + inline def apply(a: Float64Array, b: Int)(using inline doCheck: BoundsCheck) = + inline if doCheck then if a.length != b then throw VectorDimensionMismatch(a.length, b) +end dimCheckLen protected[vecxt] object indexCheck: inline def apply[A](a: Float64Array, idx: Int)(using inline doCheck: BoundsCheck) = diff --git a/vecxt/js/src/matrix.scala b/vecxt/js/src/matrix.scala index b413218..1decf78 100644 --- a/vecxt/js/src/matrix.scala +++ b/vecxt/js/src/matrix.scala @@ -124,19 +124,17 @@ object matrix: inline def numel: Int = m._1.length - /** element retrieval - */ - inline def apply(b: Tuple2[Int, Int])(using inline boundsCheck: BoundsCheck) = - indexCheckMat(m, b) - val idx = b._1 * m._2._2 + b._2 - m._1(idx) - end apply + inline def tupleFromIdx(b: Int)(using inline boundsCheck: BoundsCheck) = + val r: NArray[Double] = m.raw + dimCheckLen(r, b) + (b / m.rows, b % m.rows) + end tupleFromIdx /** element update */ inline def update(loc: Tuple2[Int, Int], value: Double)(using inline boundsCheck: BoundsCheck) = indexCheckMat(m, loc) - val idx = loc._1 * m._2._2 + loc._2 + val idx = loc._2 * m.rows + loc._1 m._1(idx) = value end update @@ -171,11 +169,15 @@ object matrix: end apply - inline def raw: NArray[Double] = m._1 - - inline def @@(b: Matrix)(using inline boundsCheck: BoundsCheck): Matrix = m.matmul(b) + /** element retrieval + */ + inline def apply(b: Tuple2[Int, Int])(using inline boundsCheck: BoundsCheck) = + indexCheckMat(m, b) + val idx = b._2 * m.rows + b._1 + m._1(idx) + end apply - inline def *=(d: Double): Unit = m._1.multInPlace(d) + inline def raw: NArray[Double] = m._1 inline def +(m2: Matrix)(using inline boundsCheck: BoundsCheck): Matrix = sameDimMatCheck(m, m2) @@ -187,51 +189,6 @@ object matrix: inline def cols: Int = m._2._2 - inline def shape: String = s"${m.rows} x ${m.cols}" - - inline def row(i: Int): NArray[Double] = - val result = new NArray[Double](m.cols) - val cols = m.cols - var j = 0 - while j < m.cols do - result(j) = m._1(i + j * m.rows) - j += 1 - end while - result - end row - - inline def print: String = - val arrArr = for i <- 0 until m.rows yield m.row(i).mkString(" ") - arrArr.mkString("\n") - end print - - inline def col(i: Int): NArray[Double] = - val result = new NArray[Double](m.rows) - val cols = m.cols - var j = 0 - while j < m.rows do - result(j) = m._1(i * m.cols + j) - j += 1 - end while - result - end col - - inline def transpose: Matrix = - val newArr = NArray.ofSize[Double](m._1.length) - var i = 0 - while i < m.cols do - var j = 0 - while j < m.rows do - newArr(i * m.rows + j) = m._1(j * m.cols + i) - j += 1 - end while - i += 1 - end while - Matrix(newArr, (m.cols, m.rows))(using - BoundsCheck.DoBoundsCheck.no - ) // we already have a valid matrix if we are transposing it, so this check is redundant if this method works as intended. - end transpose - inline def matmul(b: Matrix)(using inline boundsCheck: BoundsCheck): Matrix = dimMatCheck(m, b) val newArr = Float64Array(m.rows * b.cols) diff --git a/vecxt/jvm-native/src/array.scala b/vecxt/jvm-native/src/array.scala new file mode 100644 index 0000000..56e4be9 --- /dev/null +++ b/vecxt/jvm-native/src/array.scala @@ -0,0 +1,6 @@ +package vecxt + +object arrayUtil: + + extension [A](d: Array[A]) def printArr: String = d.mkString("[", ",", "]") +end arrayUtil diff --git a/vecxt/jvm-native/src/dimCheck.scala b/vecxt/jvm-native/src/dimCheck.scala index c887f4f..df6866b 100644 --- a/vecxt/jvm-native/src/dimCheck.scala +++ b/vecxt/jvm-native/src/dimCheck.scala @@ -19,6 +19,11 @@ package vecxt import vecxt.matrix.* import vecxt.BoundsCheck.BoundsCheck +protected[vecxt] object dimCheckLen: + inline def apply[A, B](a: Array[A], b: Int)(using inline doCheck: BoundsCheck) = + inline if doCheck then if a.length != b then throw VectorDimensionMismatch(a.length, b) +end dimCheckLen + protected[vecxt] object dimCheck: inline def apply[A, B](a: Array[A], b: Array[B])(using inline doCheck: BoundsCheck) = inline if doCheck then if a.length != b.length then throw VectorDimensionMismatch(a.length, b.length) diff --git a/vecxt/jvm/src/matrix.scala b/vecxt/jvm/src/matrix.scala index 6295124..2897dff 100644 --- a/vecxt/jvm/src/matrix.scala +++ b/vecxt/jvm/src/matrix.scala @@ -14,13 +14,6 @@ import vecxt.BoundsCheck.BoundsCheck object matrix: - export vecxt.matrixUtil.* - - type TupleOfInts[T <: Tuple] <: Boolean = T match - case EmptyTuple => true // Base case: Empty tuple is valid - case Int *: tail => TupleOfInts[tail] // Recursive case: Head is Int, check the tail - case _ => false // If any element is not an Int, return false - // opaque type Tensor = (NArray[Double], Tuple) // object Tensor: // def apply[T <: Tuple](a: NArray[Double], b: T)(using ev: TupleOfInts[T] =:= true): Tensor = (a, b) @@ -40,10 +33,6 @@ object matrix: */ opaque type Matrix = (NArray[Double], Tuple2[Int, Int]) - // type RangeExtender = Range | Int | NArray[Int] | ::.type - - // type Matrix = Matrix1 & Tensor - object Matrix: inline def doubleSpecies = DoubleVector.SPECIES_PREFERRED @@ -132,19 +121,16 @@ object matrix: inline def numel: Int = m._1.length - /** element retrieval - */ - inline def apply(b: Tuple2[Int, Int])(using inline boundsCheck: BoundsCheck) = - indexCheckMat(m, b) - val idx = b._1 * m._2._2 + b._2 - m._1(idx) - end apply + inline def tupleFromIdx(b: Int)(using inline boundsCheck: BoundsCheck) = + dimCheckLen(m.raw, b) + (b / m.rows, b % m.rows) + end tupleFromIdx /** element update */ inline def update(loc: Tuple2[Int, Int], value: Double)(using inline boundsCheck: BoundsCheck) = indexCheckMat(m, loc) - val idx = loc._1 * m._2._2 + loc._2 + val idx = loc._2 * m.rows + loc._1 m._1(idx) = value end update @@ -158,7 +144,7 @@ object matrix: var i = 0 while i < newCols.length do val colpos = newCols(i) - val stride = colpos * m.cols + val stride = colpos * m.rows var j = 0 while j < newRows.length do val rowPos = newRows(j) @@ -173,6 +159,14 @@ object matrix: end apply + /** element retrieval + */ + inline def apply(b: Tuple2[Int, Int])(using inline boundsCheck: BoundsCheck) = + indexCheckMat(m, b) + val idx = b._2 * m.rows + b._1 + m._1(idx) + end apply + inline def raw: NArray[Double] = m._1 inline def +(m2: Matrix)(using inline boundsCheck: BoundsCheck): Matrix = @@ -185,8 +179,6 @@ object matrix: inline def cols: Int = m._2._2 - inline def shape: String = s"${m.rows} x ${m.cols}" - inline def matmul(b: Matrix)(using inline boundsCheck: BoundsCheck): Matrix = dimMatCheck(m, b) val newArr = Array.ofDim[Double](m.rows * b.cols) diff --git a/vecxt/native/src/matrix.scala b/vecxt/native/src/matrix.scala index ac1a3fd..111dbde 100644 --- a/vecxt/native/src/matrix.scala +++ b/vecxt/native/src/matrix.scala @@ -7,26 +7,9 @@ import vecxt.BoundsCheck.BoundsCheck import org.ekrich.blas.unsafe.blas import org.ekrich.blas.unsafe.blasEnums import scala.scalanative.unsafe.* +import vecxt.matrixUtil.* object matrix: - - type TupleOfInts[T <: Tuple] <: Boolean = T match - case EmptyTuple => true // Base case: Empty tuple is valid - case Int *: tail => TupleOfInts[tail] // Recursive case: Head is Int, check the tail - case _ => false // If any element is not an Int, return false - - // opaque type Tensor = (NArray[Double], Tuple) - // object Tensor: - // def apply[T <: Tuple](a: NArray[Double], b: T)(using ev: TupleOfInts[T] =:= true): Tensor = (a, b) - // end Tensor - - // opaque type Vector = (NArray[Double], Tuple1[Int]) - // type Vector = Vector1 & Tensor - - // object Vector: - // def apply(a: NArray[Double]): Vector = (a, Tuple1(a.size)) - // end Vector - /** This is a matrix * * ._1 is the matrix values, stored as a single contiguous array ._2 is the dimensions ._2._1 is the number of rows @@ -40,16 +23,14 @@ object matrix: object Matrix: - inline def doubleSpecies = DoubleVector.SPECIES_PREFERRED - - inline def apply[T <: Tuple2[Int, Int]](raw: NArray[Double], dim: T)(using inline boundsCheck: BoundsCheck)(using - ev: TupleOfInts[T] =:= true + inline def apply[T <: Tuple2[Int, Int]](raw: NArray[Double], dim: T)(using + inline boundsCheck: BoundsCheck ): Matrix = dimMatInstantiateCheck(raw, dim) (raw, dim) end apply - inline def apply[T <: Tuple2[Int, Int]](dim: T, raw: NArray[Double])(using inline boundsCheck: BoundsCheck)(using - ev: TupleOfInts[T] =:= true + inline def apply[T <: Tuple2[Int, Int]](dim: T, raw: NArray[Double])(using + inline boundsCheck: BoundsCheck ): Matrix = dimMatInstantiateCheck(raw, dim) (raw, dim) @@ -126,19 +107,16 @@ object matrix: inline def numel: Int = m._1.length - /** element retrieval - */ - inline def apply(b: Tuple2[Int, Int])(using inline boundsCheck: BoundsCheck) = - indexCheckMat(m, b) - val idx = b._1 * m._2._2 + b._2 - m._1(idx) - end apply + inline def tupleFromIdx(b: Int)(using inline boundsCheck: BoundsCheck) = + dimCheckLen(m.raw, b) + (b / m.rows, b % m.rows) + end tupleFromIdx /** element update */ inline def update(loc: Tuple2[Int, Int], value: Double)(using inline boundsCheck: BoundsCheck) = indexCheckMat(m, loc) - val idx = loc._1 * m._2._2 + loc._2 + val idx = loc._2 * m.rows + loc._1 m._1(idx) = value end update @@ -173,11 +151,15 @@ object matrix: end apply - inline def raw: NArray[Double] = m._1 - - inline def @@(b: Matrix)(using inline boundsCheck: BoundsCheck): Matrix = m.matmul(b) + /** element retrieval + */ + inline def apply(b: Tuple2[Int, Int])(using inline boundsCheck: BoundsCheck) = + indexCheckMat(m, b) + val idx = b._2 * m.rows + b._1 + m._1(idx) + end apply - inline def *=(d: Double): Unit = m._1.multInPlace(d) + inline def raw: NArray[Double] = m._1 inline def +(m2: Matrix)(using inline boundsCheck: BoundsCheck): Matrix = sameDimMatCheck(m, m2) @@ -189,51 +171,6 @@ object matrix: inline def cols: Int = m._2._2 - inline def shape: String = s"${m.rows} x ${m.cols}" - - inline def row(i: Int): NArray[Double] = - val result = new NArray[Double](m.cols) - val cols = m.cols - var j = 0 - while j < m.cols do - result(j) = m._1(i + j * m.rows) - j += 1 - end while - result - end row - - inline def print: String = - val arrArr = for i <- 0 until m.rows yield m.row(i).mkString(" ") - arrArr.mkString("\n") - end print - - inline def col(i: Int): NArray[Double] = - val result = new NArray[Double](m.rows) - val cols = m.cols - var j = 0 - while j < m.rows do - result(j) = m._1(i * m.cols + j) - j += 1 - end while - result - end col - - inline def transpose: Matrix = - val newArr = NArray.ofSize[Double](m._1.length) - var i = 0 - while i < m.cols do - var j = 0 - while j < m.rows do - newArr(i * m.rows + j) = m._1(j * m.cols + i) - j += 1 - end while - i += 1 - end while - Matrix(newArr, (m.cols, m.rows))(using - BoundsCheck.DoBoundsCheck.no - ) // we already have a valid matrix if we are transposing it, so this check is redundant if this method works as intended. - end transpose - inline def matmul(b: Matrix)(using inline boundsCheck: BoundsCheck): Matrix = dimMatCheck(m, b) val newArr = Array.ofDim[Double](m.rows * b.cols) diff --git a/vecxt/src/matrixutil.scala b/vecxt/src/matrixutil.scala index 1432c8d..d69493b 100644 --- a/vecxt/src/matrixutil.scala +++ b/vecxt/src/matrixutil.scala @@ -6,37 +6,38 @@ import vecxt.BoundsCheck import vecxt.arrays.multInPlace import vecxt.matrix.matmul import vecxt.BoundsCheck.BoundsCheck - +// import vecxt.arrayUtil.printArr object matrixUtil: - extension [A](d: Array[A]) def print: String = d.mkString("[", ",", "],") - extension (m: Matrix) inline def transpose: Matrix = + import vecxt.BoundsCheck.DoBoundsCheck.no + val newMat = Matrix.zeros(m.cols, m.rows) + var idx = 0 - val newArr = NArray.ofSize[Double](m.numel) - var i = 0 - while i < m.cols do - var j = 0 - while j < m.rows do - newArr(i * m.rows + j) = m.raw(j * m.cols + i) - j += 1 - end while - i += 1 + while idx < newMat.numel do + val positionNew = m.tupleFromIdx(idx) + newMat(positionNew) = m((positionNew._2, positionNew._1)) + + idx += 1 end while - Matrix(newArr, (m.cols, m.rows))(using - BoundsCheck.DoBoundsCheck.no - ) // we already have a valid matrix if we are transposing it, so this check is redundant if this method works as intended. + newMat end transpose inline def row(i: Int): NArray[Double] = + // println(s"row $i") m(i, ::).raw end row - inline def print: String = - val arrArr = for i <- 0 until m.rows yield m.row(i).mkString(" ") + inline def printMat: String = + val arrArr = + for i <- 0 until m.rows + yield + // println(m.row(i).printArr) + m.row(i).mkString(" ") + end arrArr arrArr.mkString("\n") - end print + end printMat inline def col(i: Int): NArray[Double] = m(::, i).raw @@ -44,7 +45,9 @@ object matrixUtil: inline def @@(b: Matrix)(using inline boundsCheck: BoundsCheck): Matrix = m.matmul(b) - inline def *=(d: Double): Unit = m.raw.multInPlace(d) + inline def *:*=(d: Double): Unit = m.raw.multInPlace(d) + + inline def shape: String = s"${m.rows} x ${m.cols}" end extension end matrixUtil diff --git a/vecxt/src/package.scala b/vecxt/src/package.scala index bac4c37..8501876 100644 --- a/vecxt/src/package.scala +++ b/vecxt/src/package.scala @@ -2,3 +2,6 @@ package vecxt.all export vecxt.matrix.* export vecxt.arrays.* +export vecxt.cosineSimilarity +export vecxt.matrixUtil.* +export vecxt.arrayUtil.* diff --git a/vecxt/test/src/array.test.scala b/vecxt/test/src/array.test.scala index 0c60e82..4453947 100644 --- a/vecxt/test/src/array.test.scala +++ b/vecxt/test/src/array.test.scala @@ -19,13 +19,20 @@ package vecxt import narr.* import scala.util.chaining.* import vecxt.arrays.* -import vecxt.matrix.print +import vecxt.arrayUtil.* class ArrayExtensionSuite extends munit.FunSuite: import BoundsCheck.DoBoundsCheck.yes lazy val v_fill = NArray[Double](0, 1, 2, 3, 4) + test("print") { + val v1 = NArray[Double](1, 2, 3) + val v2 = NArray[Double](4, 5, 6) + val out = (v1 + v2).printArr + assert(out.contains("5")) + } + test("Array horizontal sum") { val v1 = NArray[Double](1.0, 2.0, 3.0) val v2 = v1 * 2.0 diff --git a/vecxt/test/src/matrix.test.scala b/vecxt/test/src/matrix.test.scala index 184b04d..201b7d5 100644 --- a/vecxt/test/src/matrix.test.scala +++ b/vecxt/test/src/matrix.test.scala @@ -3,11 +3,52 @@ package vecxt import munit.FunSuite import narr.* import vecxt.matrix.* -import vecxt.arrays.* +import vecxt.arrays.`*` import vecxt.BoundsCheck.DoBoundsCheck.yes +import vecxt.matrixUtil.* +import vecxt.arrayUtil.* class TensorExtensionSuite extends FunSuite: + // TODO will fail on JS, grrr. + test("print") { + val mat1 = Matrix(NArray(1.0, 4.0, 2.0, 5.0), (2, 2)) + assert(mat1.printMat.contains("4")) + + } + + // test("transpose etc".only) { + // val mat1 = Matrix(NArray(1.0, 4.0, 2.0, 5.0, 3.0, 6.0), (2, 3)) + // val mat2 = Matrix(NArray(7.0, 9.0, 11.0, 8.0, 10, 12.0), (3, 2)) + // val result2 = mat1 @@ mat2 + + // result2.printMat + // val result3 = Matrix.eye(2) + mat1 @@ mat2 + // result3.printMat + // val mat3 = mat2.transpose + mat1 + // println(mat2.transpose.printMat) + // mat3.raw.printArr + // mat3.printMat + // } + + test("element access") { + + val orig = Matrix.fromRows( + NArray( + NArray(2.0, 0.0, -1.0), + NArray(0.0, 3.0, 4.0) + ) + ) + + assertEqualsDouble(orig((0, 0)), 2, 0.0001) + assertEqualsDouble(orig((0, 1)), 0, 0.0001) + assertEqualsDouble(orig((0, 2)), -1, 0.0001) + assertEqualsDouble(orig((1, 0)), 0, 0.0001) + assertEqualsDouble(orig((1, 1)), 3, 0.0001) + assertEqualsDouble(orig((1, 2)), 4, 0.0001) + + } + test("operator precedance") { val mat1 = Matrix.eye(2) val mat2 = Matrix(mat1.raw * 2, (mat1.rows, mat1.cols)) @@ -78,18 +119,25 @@ class TensorExtensionSuite extends FunSuite: assertVecEquals(result.raw, NArray(58.0, 139.0, 64.0, 154.0)) } - test("Matrix transpose") { - val originalArray = NArray[Double](1, 2, 3, 4, 5, 6) - val matrix = Matrix(originalArray, (2, 3)) + test("Matrix transpose".only) { + val orig = Matrix.fromRows( + NArray( + NArray(2.0, 0.0, -1.0), + NArray(0.0, 3.0, 4.0) + ) + ) + val transpose = Matrix.fromRows( + NArray( + NArray(2.0, 0.0), + NArray(0.0, 3.0), + NArray(-1.0, 4.0) + ) + ) - val transposedMatrix = matrix.transpose + val transposedMatrix = orig.transpose - val expectedArray = NArray[Double](1, 4, 2, 5, 3, 6) - val expectedMatrix = Matrix(expectedArray, (3, 2)) + assertEquals(transposedMatrix.raw.toList, transpose.raw.toList) - assertEquals(transposedMatrix.raw.toList, expectedMatrix.raw.toList) - assertEquals(transposedMatrix.rows, expectedMatrix.rows) - assertEquals(transposedMatrix.cols, expectedMatrix.cols) } test("Tensor raw array retrieval") { @@ -99,7 +147,12 @@ class TensorExtensionSuite extends FunSuite: } test("Tensor elementAt retrieval for 2D tensor") { - val tensor = Matrix(NArray[Double](1.0, 2.0, 3.0, 4.0), (2, 2)) + val tensor = Matrix.fromRows( + NArray( + NArray[Double](1.0, 2.0), + NArray[Double](3.0, 4.0) + ) + ) assertEquals(tensor((0, 0)), 1.0) assertEquals(tensor((0, 1)), 2.0) assertEquals(tensor((1, 0)), 3.0) @@ -175,13 +228,13 @@ class TensorExtensionSuite extends FunSuite: test("matrix scale") { val array = NArray[Double](1.0, 2.0, 3.0, 4.0) val matrix = Matrix(array, (2, 2)) - val col1 = matrix *= (2) + val col1 = matrix *:*= (2) assertVecEquals(matrix.raw, NArray[Double](2.0, 4.0, 6.0, 8.0)) } test("matrix ops") { val mat1 = Matrix.eye(3) // multiplication in place - mat1 *= 2 + mat1 *:*= 2 val mat2 = Matrix.eye(3) + Matrix.eye(3) // addition assertVecEquals(mat1.raw, mat2.raw) }