From a0b898828de12fa0586c741162d28c94c8ea1e60 Mon Sep 17 00:00:00 2001 From: Simon Parten Date: Wed, 12 Aug 2026 14:33:37 +0000 Subject: [PATCH 1/4] implementations of last ??? --- laws/src/instances/DoubleInstances.scala | 2 +- vecxt/src-jvm/floatmatrix.scala | 6 --- vecxt/src/doublematrix.scala | 65 ++++++++++++++++++++--- vecxt/test/src-jvm/TODO.test.scala | 20 ------- vecxt/test/src-jvm/floatmatrix.test.scala | 13 +++++ vecxt/test/src/matrix.test.scala | 47 +++------------- vecxt_re/test/src/tower.test.scala | 1 - 7 files changed, 78 insertions(+), 76 deletions(-) diff --git a/laws/src/instances/DoubleInstances.scala b/laws/src/instances/DoubleInstances.scala index c68f8dc5..8653bb29 100644 --- a/laws/src/instances/DoubleInstances.scala +++ b/laws/src/instances/DoubleInstances.scala @@ -1,6 +1,6 @@ package vecxt.laws.instances -import vecxt.all.{*, given} +import vecxt.all.* import vecxt.laws.Dimension import vecxt.laws.VectorCommutativeGroup import vecxt.laws.VectorCommutativeMonoid diff --git a/vecxt/src-jvm/floatmatrix.scala b/vecxt/src-jvm/floatmatrix.scala index 4b416715..6ade50ab 100644 --- a/vecxt/src-jvm/floatmatrix.scala +++ b/vecxt/src-jvm/floatmatrix.scala @@ -578,12 +578,6 @@ object JvmFloatMatrix: end -= - /** In-place elementwise scalar multiply. The non-contiguous branch used to be `???`, which made the scalar-left - * `d *= m` below (which delegates here) throw for any strided or offset matrix even though nothing about the - * operation needs contiguity. Same shape as the `Double` twin in `src/doublematrix.scala`: SIMD over the whole - * backing array when dense contiguous, element-by-element via `linearIndex` otherwise — which skips padding - * instead of scaling it. - */ def *=(d: Float): Unit = if m.hasSimpleContiguousMemoryLayout then floatarrays.*=(m.raw)(d) else diff --git a/vecxt/src/doublematrix.scala b/vecxt/src/doublematrix.scala index f5431b1e..c78fdc7b 100644 --- a/vecxt/src/doublematrix.scala +++ b/vecxt/src/doublematrix.scala @@ -342,7 +342,12 @@ object DoubleMatrix: def unary_- : Matrix[Double] = if m.hasSimpleContiguousMemoryLayout then Matrix[Double](vecxt.doublearrays.unary_-(m.raw), m.layout) - else ??? + else + m.layout.foreach2D { (i, j) => + val idx = m.layout.linearIndex(i, j) + m.raw(idx) = -m.raw(idx) + } + m def `exp!`: Unit = if m.hasSimpleContiguousMemoryLayout then vecxt.doublearrays.`exp!`(m.raw) @@ -443,19 +448,47 @@ object DoubleMatrix: def tan = if m.hasSimpleContiguousMemoryLayout then Matrix[Double](vecxt.all.tan(m.raw), m.layout) - else ??? + else + val newArr = Array.ofDim[Double](m.numel) + m.layout.foreach2D { (i, j) => + val srcIdx = m.layout.linearIndex(i, j) + newArr(i + j * m.rows) = Math.tan(m.raw(srcIdx)) + } + Matrix[Double](newArr, m.rows, m.cols) def `tan!` = if m.hasSimpleContiguousMemoryLayout then vecxt.doublearrays.`tan!`(m.raw) - else ??? + else + m.layout.foreach2D { (i, j) => + val idx = m.layout.linearIndex(i, j) + m.raw(idx) = Math.tan(m.raw(idx)) + } + end if + end `tan!` def mean: Double = if m.hasSimpleContiguousMemoryLayout then m.sumSIMD / (m.rows * m.cols) - else ??? + else + var acc = 0.0 + m.layout.foreach2D { (i, j) => + val idx = m.layout.linearIndex(i, j) + acc += m.raw(idx) + } + acc / (m.rows * m.cols) + end if + end mean def **(power: Double): Matrix[Double] = if m.hasSimpleContiguousMemoryLayout then Matrix[Double](vecxt.all.**(m.raw)(power), m.layout) - else ??? + else + val newArr = Array.ofDim[Double](m.numel) + m.layout.foreach2D { (i, j) => + val srcIdx = m.layout.linearIndex(i, j) + newArr(i + j * m.rows) = Math.pow(m.raw(srcIdx), power) + } + Matrix[Double](newArr, m.rows, m.cols) + end if + end ** /** Reads every element through `m.layout.linearIndex`, which is just `offset + row * rowStride + col * colStride` — * valid for any layout, dense or strided, row-major or column-major. So unlike the element-wise SIMD ops in this @@ -513,15 +546,31 @@ object DoubleMatrix: m.diag.sum end trace - def sum: Double = sumSIMD + inline def sum: Double = sumSIMD def sumSIMD: Double = if m.hasSimpleContiguousMemoryLayout then vecxt.doublearrays.sum(m.raw) - else ??? + else + var acc = 0.0 + m.layout.foreach2D { (i, j) => + val idx = m.layout.linearIndex(i, j) + acc += m.raw(idx) + } + acc + end if + end sumSIMD def norm: Double = if m.hasSimpleContiguousMemoryLayout then vecxt.all.norm(m.raw) - else ??? + else + var acc = 0.0 + m.layout.foreach2D { (i, j) => + val idx = m.layout.linearIndex(i, j) + acc += m.raw(idx) * m.raw(idx) + } + Math.sqrt(acc) + end if + end norm // Note: det method is provided by platform-specific implementations // See: vecxt.JvmDeterminant (JVM with SIMD) and vecxt.JsNativeDeterminant (JS/Native) diff --git a/vecxt/test/src-jvm/TODO.test.scala b/vecxt/test/src-jvm/TODO.test.scala index 763dbac6..4e5dd110 100644 --- a/vecxt/test/src-jvm/TODO.test.scala +++ b/vecxt/test/src-jvm/TODO.test.scala @@ -29,25 +29,5 @@ class TODO extends FunSuite: left.`matmulInPlace!`(right, out, alpha = 1.0f, beta = 0.0f) } - // Removed: "matrix-vector multiply throws for non-column-major Float matrices". It asserted a NotImplementedError - // for a dense row-major operand, which is precisely the `???` this branch replaced — `*` now picks TRANS and lda - // from the strides and handles that layout, so the test was pinning the limitation rather than any behaviour worth - // keeping. The positive case is covered in the shared FloatMatVecSuite, which runs the same 2x3 row-major shape - // (among five layouts) and checks the values rather than just that something happens. - - // Was: "*= scalar throws for unsupported non-contiguous Float layouts", asserting a NotImplementedError for the - // fixture below. That `???` is now a `foreach2D` fallback (needed so the scalar-left `d *= m` works for anything but - // dense contiguous matrices), so the test asserts the values instead of the limitation. - test("*= scalar scales every element of a non-contiguous Float layout, and nothing else"): - // rows=2, cols=2, rowStride=2, colStride=5 over a length-8 array: neither stride is 1, so this is also the - // `unitStrideAxis == -1` case. Elements live at raw(0), raw(2), raw(5), raw(7); the 90s are padding. - val raw = Array[Float](1.0f, 90.0f, 2.0f, 91.0f, 92.0f, 3.0f, 93.0f, 4.0f) - val mat = Matrix[Float](raw, 2, 2, 2, 5, 0) - assert(!mat.hasSimpleContiguousMemoryLayout) - - mat *= 2.0f - - assertMatrixEquals(mat, Matrix.fromRows[Float](Array(2.0f, 6.0f), Array(4.0f, 8.0f))) - assertVecEquals(raw, Array[Float](2.0f, 90.0f, 4.0f, 91.0f, 92.0f, 6.0f, 93.0f, 8.0f)) end TODO diff --git a/vecxt/test/src-jvm/floatmatrix.test.scala b/vecxt/test/src-jvm/floatmatrix.test.scala index 25e46bef..c94651ac 100644 --- a/vecxt/test/src-jvm/floatmatrix.test.scala +++ b/vecxt/test/src-jvm/floatmatrix.test.scala @@ -130,6 +130,19 @@ class FloatMatrixJvmSuite extends FunSuite: ) ) + + test("*= scalar scales every element of a non-contiguous Float layout, and nothing else"): + // rows=2, cols=2, rowStride=2, colStride=5 over a length-8 array: neither stride is 1, so this is also the + // `unitStrideAxis == -1` case. Elements live at raw(0), raw(2), raw(5), raw(7); the 90s are padding. + val raw = Array[Float](1.0f, 90.0f, 2.0f, 91.0f, 92.0f, 3.0f, 93.0f, 4.0f) + val mat = Matrix[Float](raw, 2, 2, 2, 5, 0) + assert(!mat.hasSimpleContiguousMemoryLayout) + + mat *= 2.0f + + assertMatrixEquals(mat, Matrix.fromRows[Float](Array(2.0f, 6.0f), Array(4.0f, 8.0f))) + assertVecEquals(raw, Array[Float](2.0f, 90.0f, 4.0f, 91.0f, 92.0f, 6.0f, 93.0f, 8.0f)) + test("*:*= on offset Float view uses general layout path"): // (vecxt/src-jvm/floatmatrix.scala) `sub` has a // nonzero offset (it's `base`'s columns 1..2), so it misses the dense/offset-0 fast path and exercises the diff --git a/vecxt/test/src/matrix.test.scala b/vecxt/test/src/matrix.test.scala index 991d704b..47ce4ff4 100644 --- a/vecxt/test/src/matrix.test.scala +++ b/vecxt/test/src/matrix.test.scala @@ -9,27 +9,6 @@ import MatrixInstance.update class MatrixExtensionSuite extends FunSuite: - // TODO will fail on JS, grrr. - // test("print") { - // val mat1 = Matrix[Double](Array(1.0, 4.0, 2.0, 5.0), (2, 2)) - // assert(mat1.printMat.contains("4")) - - // } - - // test("transpose etc".only) { - // val mat1 = Matrix(Array(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 - // } - def mat1to9 = Matrix.fromRows[Double]( Array(1.0, 2.0, 3.0), Array(4.0, 5.0, 6.0), @@ -39,7 +18,11 @@ class MatrixExtensionSuite extends FunSuite: def raw1to9 = mat1to9.raw test("pow") { - mat1to9 ** 2.0 + assertVecEquals[Double]((mat1to9 ** 2.0).raw, raw1to9.map(x => x * x)) + + val dontMutate = mat1to9 + assertVecEquals[Double]((dontMutate(1 to 2, 1 to 2) ** 2.0).deepCopy.raw, Array(25.0, 36.0, 49.0, 64.0)) + assertEqualsDouble(dontMutate(1,0), 4.0, 0.01) } test("from rows") { @@ -283,17 +266,6 @@ class MatrixExtensionSuite extends FunSuite: assertMatrixEquals(result, expected) } - test("Some urnary ops") { - val checkThis = mat1to9.exp - mat1to9.log - mat1to9.sqrt - mat1to9.sin - mat1to9.cos - - assertVecEquals[Double](checkThis.raw, raw1to9.exp) - - } - test("log on submatrix (non-contiguous layout) yields correct new matrix") { val base = Matrix[Double](Array.tabulate[Double](9)(_.toDouble), 3, 3) val sub = base(::, Array(1, 2)) // cols 1&2 of col-major 3x3: values (3,4,5) and (6,7,8) @@ -582,17 +554,12 @@ class MatrixExtensionSuite extends FunSuite: val arr2 = Array[Double](1.0, 2.0) - assertVecEquals(mat1 * arr1, Array[Double](14.0, 32.0)) - - // Was commented out because `*` threw `???` for anything that was not dense column-major, and mat1.transpose is - // row-major. It now runs — but note the value it was written with, Array(6.0, 30.0), was never right: the - // transpose is 3x2, so the product with a length-2 vector has three entries, not two. - // mat1.transpose is [[1,4],[2,5],[3,6]]; against [1,2] that is [1+8, 2+10, 3+12]. + assertVecEquals(mat1 * arr1, Array[Double](14.0, 32.0)) assertVecEquals(mat1.transpose * arr2, Array[Double](9.0, 12.0, 15.0)) } // ─── matrix-vector product across layouts ──────────────────────────────────────────────────────────────────── - // `*` used to be `if m.isDenseColMajor then dgemv(...) else ???`. It now picks TRANS/lda from the strides, the + // Picks TRANS/lda from the strides, the // same way matmulInPlace! does for dgemm, and falls back to an elementwise loop for layouts no single leading // dimension can describe. Every fixture below is the same logical 2x3 [[1,2,3],[4,5,6]], so all must agree. diff --git a/vecxt_re/test/src/tower.test.scala b/vecxt_re/test/src/tower.test.scala index f8f929d7..44b46d28 100644 --- a/vecxt_re/test/src/tower.test.scala +++ b/vecxt_re/test/src/tower.test.scala @@ -2,7 +2,6 @@ package vecxt_re import vecxt_re.* import vecxt.all.* -import vecxt.all.given import SplitLosses.* class TowerSuite extends munit.FunSuite: From 0dfb72b4d5389d7c5f631349f804504801155afe Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 14:35:58 +0000 Subject: [PATCH 2/4] [autofix.ci] apply automated fixes --- vecxt/src/doublematrix.scala | 12 ++++++------ vecxt/test/src-jvm/TODO.test.scala | 1 - vecxt/test/src-jvm/floatmatrix.test.scala | 1 - vecxt/test/src/matrix.test.scala | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/vecxt/src/doublematrix.scala b/vecxt/src/doublematrix.scala index c78fdc7b..044a3c6d 100644 --- a/vecxt/src/doublematrix.scala +++ b/vecxt/src/doublematrix.scala @@ -448,7 +448,7 @@ object DoubleMatrix: def tan = if m.hasSimpleContiguousMemoryLayout then Matrix[Double](vecxt.all.tan(m.raw), m.layout) - else + else val newArr = Array.ofDim[Double](m.numel) m.layout.foreach2D { (i, j) => val srcIdx = m.layout.linearIndex(i, j) @@ -458,7 +458,7 @@ object DoubleMatrix: def `tan!` = if m.hasSimpleContiguousMemoryLayout then vecxt.doublearrays.`tan!`(m.raw) - else + else m.layout.foreach2D { (i, j) => val idx = m.layout.linearIndex(i, j) m.raw(idx) = Math.tan(m.raw(idx)) @@ -468,7 +468,7 @@ object DoubleMatrix: def mean: Double = if m.hasSimpleContiguousMemoryLayout then m.sumSIMD / (m.rows * m.cols) - else + else var acc = 0.0 m.layout.foreach2D { (i, j) => val idx = m.layout.linearIndex(i, j) @@ -480,7 +480,7 @@ object DoubleMatrix: def **(power: Double): Matrix[Double] = if m.hasSimpleContiguousMemoryLayout then Matrix[Double](vecxt.all.**(m.raw)(power), m.layout) - else + else val newArr = Array.ofDim[Double](m.numel) m.layout.foreach2D { (i, j) => val srcIdx = m.layout.linearIndex(i, j) @@ -550,7 +550,7 @@ object DoubleMatrix: def sumSIMD: Double = if m.hasSimpleContiguousMemoryLayout then vecxt.doublearrays.sum(m.raw) - else + else var acc = 0.0 m.layout.foreach2D { (i, j) => val idx = m.layout.linearIndex(i, j) @@ -562,7 +562,7 @@ object DoubleMatrix: def norm: Double = if m.hasSimpleContiguousMemoryLayout then vecxt.all.norm(m.raw) - else + else var acc = 0.0 m.layout.foreach2D { (i, j) => val idx = m.layout.linearIndex(i, j) diff --git a/vecxt/test/src-jvm/TODO.test.scala b/vecxt/test/src-jvm/TODO.test.scala index 4e5dd110..849f1c4e 100644 --- a/vecxt/test/src-jvm/TODO.test.scala +++ b/vecxt/test/src-jvm/TODO.test.scala @@ -29,5 +29,4 @@ class TODO extends FunSuite: left.`matmulInPlace!`(right, out, alpha = 1.0f, beta = 0.0f) } - end TODO diff --git a/vecxt/test/src-jvm/floatmatrix.test.scala b/vecxt/test/src-jvm/floatmatrix.test.scala index c94651ac..0ceecdd0 100644 --- a/vecxt/test/src-jvm/floatmatrix.test.scala +++ b/vecxt/test/src-jvm/floatmatrix.test.scala @@ -130,7 +130,6 @@ class FloatMatrixJvmSuite extends FunSuite: ) ) - test("*= scalar scales every element of a non-contiguous Float layout, and nothing else"): // rows=2, cols=2, rowStride=2, colStride=5 over a length-8 array: neither stride is 1, so this is also the // `unitStrideAxis == -1` case. Elements live at raw(0), raw(2), raw(5), raw(7); the 90s are padding. diff --git a/vecxt/test/src/matrix.test.scala b/vecxt/test/src/matrix.test.scala index 47ce4ff4..abe8f8ab 100644 --- a/vecxt/test/src/matrix.test.scala +++ b/vecxt/test/src/matrix.test.scala @@ -22,7 +22,7 @@ class MatrixExtensionSuite extends FunSuite: val dontMutate = mat1to9 assertVecEquals[Double]((dontMutate(1 to 2, 1 to 2) ** 2.0).deepCopy.raw, Array(25.0, 36.0, 49.0, 64.0)) - assertEqualsDouble(dontMutate(1,0), 4.0, 0.01) + assertEqualsDouble(dontMutate(1, 0), 4.0, 0.01) } test("from rows") { @@ -554,7 +554,7 @@ class MatrixExtensionSuite extends FunSuite: val arr2 = Array[Double](1.0, 2.0) - assertVecEquals(mat1 * arr1, Array[Double](14.0, 32.0)) + assertVecEquals(mat1 * arr1, Array[Double](14.0, 32.0)) assertVecEquals(mat1.transpose * arr2, Array[Double](9.0, 12.0, 15.0)) } From dd694579e798113d8af560282bb3645c420b9f63 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Aug 2026 14:48:21 +0000 Subject: [PATCH 3/4] Improve layout-corpus coverage for newly implemented matrix paths Co-authored-by: Quafadas <24899792+Quafadas@users.noreply.github.com> --- vecxt/src/doublematrix.scala | 7 +++-- vecxt/test/src/layoutCorpus.test.scala | 43 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/vecxt/src/doublematrix.scala b/vecxt/src/doublematrix.scala index 044a3c6d..4ddb6277 100644 --- a/vecxt/src/doublematrix.scala +++ b/vecxt/src/doublematrix.scala @@ -343,11 +343,12 @@ object DoubleMatrix: def unary_- : Matrix[Double] = if m.hasSimpleContiguousMemoryLayout then Matrix[Double](vecxt.doublearrays.unary_-(m.raw), m.layout) else + val newArr = Array.ofDim[Double](m.numel) m.layout.foreach2D { (i, j) => - val idx = m.layout.linearIndex(i, j) - m.raw(idx) = -m.raw(idx) + val srcIdx = m.layout.linearIndex(i, j) + newArr(i + j * m.rows) = -m.raw(srcIdx) } - m + Matrix[Double](newArr, m.rows, m.cols) def `exp!`: Unit = if m.hasSimpleContiguousMemoryLayout then vecxt.doublearrays.`exp!`(m.raw) diff --git a/vecxt/test/src/layoutCorpus.test.scala b/vecxt/test/src/layoutCorpus.test.scala index 2a72ede7..6424484b 100644 --- a/vecxt/test/src/layoutCorpus.test.scala +++ b/vecxt/test/src/layoutCorpus.test.scala @@ -289,6 +289,38 @@ class LayoutCorpusSuite extends FunSuite: end for } + test("tan — op(view) == op(copy) over the full layout-kind corpus") { + for m <- corpus do + val copy = denseCopy(m) + assertLogicallyEqual(m.tan, copy.tan, s"tan on $m") + end for + } + + test("unary_- — op(view) == op(copy), source matrix unchanged") { + for m <- corpus do + val copy = denseCopy(m) + val before = m.raw.clone() + assertLogicallyEqual(-m, copy.*(-1.0), s"unary_- on $m") + assertVecEquals(m.raw, before) + end for + } + + test("**(scalar) — op(view) == op(copy) over the full layout-kind corpus") { + for m <- corpus do + val copy = denseCopy(m) + assertLogicallyEqual(m.**(2.0), copy.**(2.0), s"**(2.0) on $m") + end for + } + + test("mean/sum/norm reductions on views match dense copy") { + for m <- corpus do + val copy = denseCopy(m) + assertEqualsDouble(m.mean, copy.mean, 1e-9, s"mean on $m") + assertEqualsDouble(m.sum, copy.sum, 1e-9, s"sum on $m") + assertEqualsDouble(m.norm, copy.norm, 1e-9, s"norm on $m") + end for + } + /** The oracle for `Matrix[Boolean]` results — mirrors `model` above but for comparison-operator output. */ private def modelBool(m: Matrix[Boolean])(i: Int, j: Int): Boolean = m.raw(m.layout.offset + i * m.layout.rowStride + j * m.layout.colStride) @@ -414,6 +446,17 @@ class LayoutCorpusSuite extends FunSuite: end for } + test("tan! in-place — in-view matches op(copy), out-of-view untouched") { + for m <- corpus do + val copy = denseCopy(m) + val before = m.raw.clone() + copy.`tan!` + m.`tan!` + assertLogicallyEqual(m, copy, s"tan! on $m") + assertOutsideViewUntouched(m, before, s"tan! on $m") + end for + } + test("update(fct, value) in-place — in-view matches op(copy), out-of-view untouched") { for m <- corpus do val copy = denseCopy(m) From b3b340aa44dfa9ff6c95f760639f328380f5c042 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:15:20 +0000 Subject: [PATCH 4/4] Fix flaky pow assertion causing CI failure Co-authored-by: Quafadas <24899792+Quafadas@users.noreply.github.com> --- vecxt/test/src/matrix.test.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vecxt/test/src/matrix.test.scala b/vecxt/test/src/matrix.test.scala index abe8f8ab..eb5aa254 100644 --- a/vecxt/test/src/matrix.test.scala +++ b/vecxt/test/src/matrix.test.scala @@ -21,7 +21,10 @@ class MatrixExtensionSuite extends FunSuite: assertVecEquals[Double]((mat1to9 ** 2.0).raw, raw1to9.map(x => x * x)) val dontMutate = mat1to9 - assertVecEquals[Double]((dontMutate(1 to 2, 1 to 2) ** 2.0).deepCopy.raw, Array(25.0, 36.0, 49.0, 64.0)) + assertMatrixEquals( + dontMutate(1 to 2, 1 to 2) ** 2.0, + Matrix.fromRows[Double](Array(25.0, 36.0), Array(64.0, 81.0)) + ) assertEqualsDouble(dontMutate(1, 0), 4.0, 0.01) }