Skip to content

Commit 6285354

Browse files
authored
Merge pull request #37 from Quafadas/copilot/fix-7ebc2283-e18a-45e5-b01d-9656d56639cf
Implement Hadamard product for matrices with different memory layouts
2 parents b0b5fbb + 0b72a91 commit 6285354

7 files changed

Lines changed: 152 additions & 5 deletions

File tree

vecxt/src-js/array_native.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,17 @@ object JsNativeDoubleArrays:
456456
res
457457
end *
458458

459+
inline def *=(d: NArray[Double])(using inline boundsCheck: BoundsCheck): Unit =
460+
dimCheck(vec, d)
461+
val n = vec.length
462+
463+
var i = 0
464+
while i < n do
465+
vec(i) = vec(i) * d(i)
466+
i += 1
467+
end while
468+
end *=
469+
459470
inline def outer(other: NArray[Double])(using ClassTag[Double]): Matrix[Double] =
460471
val n = vec.length
461472
val m = other.length

vecxt/src-jvm/arrays.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,23 @@ object arrays:
11761176
out
11771177
end *
11781178

1179+
inline def *=(d: Array[Double])(using inline boundsCheck: BoundsCheck): Unit =
1180+
dimCheck(vec, d)
1181+
var i = 0
1182+
while i < spd.loopBound(vec.length) do
1183+
DoubleVector
1184+
.fromArray(spd, vec, i)
1185+
.mul(DoubleVector.fromArray(spd, d, i))
1186+
.intoArray(vec, i)
1187+
i += spdl
1188+
end while
1189+
1190+
while i < vec.length do
1191+
vec(i) = vec(i) * d(i)
1192+
i = i + 1
1193+
end while
1194+
end *=
1195+
11791196
inline def /(d: Array[Double])(using inline boundsCheck: BoundsCheck): Array[Double] =
11801197
dimCheck(vec, d)
11811198
val out = new Array[Double](vec.length)

vecxt/src-native/array_js_native.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,17 @@ object JsNativeDoubleArrays:
456456
res
457457
end *
458458

459+
inline def *=(d: NArray[Double])(using inline boundsCheck: BoundsCheck): Unit =
460+
dimCheck(vec, d)
461+
val n = vec.length
462+
463+
var i = 0
464+
while i < n do
465+
vec(i) = vec(i) * d(i)
466+
i += 1
467+
end while
468+
end *=
469+
459470
inline def outer(other: NArray[Double])(using ClassTag[Double]): Matrix[Double] =
460471
val n = vec.length
461472
val m = other.length

vecxt/src/MatrixInstance.scala

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,23 @@ object MatrixInstance:
127127
* @return
128128
*/
129129
def deepCopy(using ct: ClassTag[A]): Matrix[A] =
130+
deepCopy(asRowMajor = false)
131+
end deepCopy
132+
133+
/** Returns a deep copy of the matrix with specified layout. Copies elements one by one.
134+
*
135+
* @param asRowMajor
136+
* If true, returns row-major layout; if false, returns column-major layout
137+
* @param ct
138+
* @return
139+
*/
140+
def deepCopy(asRowMajor: Boolean)(using ct: ClassTag[A]): Matrix[A] =
130141
// println(s"Deep copying matrix with shape ${m.shape} and offset ${m.offset}")
131142
import BoundsCheck.DoBoundsCheck.no
132143
val newRaw = NArray.ofSize[A](m.numel)
133-
val newMat = Matrix(newRaw, m.rows, m.cols, 1, m.rows, 0)
144+
val newMat =
145+
if asRowMajor then Matrix(newRaw, m.rows, m.cols, m.cols, 1, 0) // row-major: rowStride = cols, colStride = 1
146+
else Matrix(newRaw, m.rows, m.cols, 1, m.rows, 0) // column-major: rowStride = 1, colStride = rows
134147
var i = 0
135148
for row <- 0 until m.rows do
136149
for col <- 0 until m.cols do

vecxt/src/doublematrix.scala

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,38 @@ object DoubleMatrix:
142142
sameDimMatCheck(m, m2)
143143

144144
if sameDenseElementWiseMemoryLayoutCheck(m, m2) then
145+
// Fast path: use SIMD-optimized array multiplication
145146
val newArr = vecxt.arrays.*(m.raw)(m2.raw)
146147
Matrix[Double](newArr, m.rows, m.cols, m.rowStride, m.colStride, m.offset)(using BoundsCheck.DoBoundsCheck.no)
147-
else ???
148+
else
149+
// Different memory layouts: materialize one matrix to match the other's layout
150+
if m.isDenseColMajor then
151+
// m is dense column-major, materialize m2 to column-major and multiply in-place
152+
val m2Dense = m2.deepCopy(asRowMajor = false)
153+
vecxt.arrays.*=(m2Dense.raw)(m.raw)
154+
m2Dense
155+
else if m.isDenseRowMajor then
156+
// m is dense row-major, materialize m2 to row-major and multiply in-place
157+
val m2Dense = m2.deepCopy(asRowMajor = true)
158+
vecxt.arrays.*=(m2Dense.raw)(m.raw)
159+
m2Dense
160+
else if m2.isDenseColMajor then
161+
// m2 is dense column-major, materialize m to column-major and multiply in-place
162+
val mDense = m.deepCopy(asRowMajor = false)
163+
vecxt.arrays.*=(mDense.raw)(m2.raw)
164+
mDense
165+
else if m2.isDenseRowMajor then
166+
// m2 is dense row-major, materialize m to row-major and multiply in-place
167+
val mDense = m.deepCopy(asRowMajor = true)
168+
vecxt.arrays.*=(mDense.raw)(m2.raw)
169+
mDense
170+
else
171+
// Neither is dense, materialize both to column-major and use SIMD multiplication
172+
val mDense = m.deepCopy(asRowMajor = false)
173+
val m2Dense = m2.deepCopy(asRowMajor = false)
174+
val newArr = vecxt.arrays.*(mDense.raw)(m2Dense.raw)
175+
Matrix[Double](newArr, m.rows, m.cols)(using BoundsCheck.DoBoundsCheck.no)
176+
end if
148177
end if
149178
end hadamard
150179

vecxt/test/src/matrix.test.scala

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,75 @@ class MatrixExtensionSuite extends FunSuite:
565565
assertVecEquals[Double](result.raw, NArray[Double](2.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 2.0))
566566
}
567567

568+
test("hadamard product with non-simple layout") {
569+
// Create base matrices
570+
val base1 = Matrix[Double](NArray.tabulate[Double](9)(i => (i + 1).toDouble), 3, 3)
571+
val base2 = Matrix[Double](NArray.tabulate[Double](9)(i => (i + 10).toDouble), 3, 3)
572+
573+
// Create views with non-simple layouts
574+
val view1 = base1(::, NArray(1, 2)) // columns 1 and 2
575+
val view2 = base2(::, NArray(1, 2)) // columns 1 and 2
576+
577+
// Compute hadamard product
578+
val result = view1.hadamard(view2)
579+
580+
// Expected values (column-major):
581+
// view1 columns 1,2 of base1 = [[4,7], [5,8], [6,9]]
582+
// view2 columns 1,2 of base2 = [[13,16], [14,17], [15,18]]
583+
// hadamard should give element-wise multiplication
584+
assert(result.rows == 3)
585+
assert(result.cols == 2)
586+
assertEqualsDouble(result(0, 0), 4.0 * 13.0, 0.0001) // 52
587+
assertEqualsDouble(result(1, 0), 5.0 * 14.0, 0.0001) // 70
588+
assertEqualsDouble(result(2, 0), 6.0 * 15.0, 0.0001) // 90
589+
assertEqualsDouble(result(0, 1), 7.0 * 16.0, 0.0001) // 112
590+
assertEqualsDouble(result(1, 1), 8.0 * 17.0, 0.0001) // 136
591+
assertEqualsDouble(result(2, 1), 9.0 * 18.0, 0.0001) // 162
592+
}
593+
594+
test("hadamard product with mixed layouts") {
595+
// One matrix with simple layout, one with non-simple
596+
val simple = Matrix[Double](NArray(1.0, 2.0, 3.0, 4.0, 5.0, 6.0), 3, 2)
597+
val base = Matrix[Double](NArray.tabulate[Double](9)(i => (i + 10).toDouble), 3, 3)
598+
val view = base(::, NArray(0, 2)) // columns 0 and 2
599+
600+
val result = simple.hadamard(view)
601+
602+
assert(result.rows == 3)
603+
assert(result.cols == 2)
604+
assertEqualsDouble(result(0, 0), 1.0 * 10.0, 0.0001)
605+
assertEqualsDouble(result(1, 0), 2.0 * 11.0, 0.0001)
606+
assertEqualsDouble(result(2, 0), 3.0 * 12.0, 0.0001)
607+
assertEqualsDouble(result(0, 1), 4.0 * 16.0, 0.0001)
608+
assertEqualsDouble(result(1, 1), 5.0 * 17.0, 0.0001)
609+
assertEqualsDouble(result(2, 1), 6.0 * 18.0, 0.0001)
610+
}
611+
612+
test("hadamard product with transposed matrix") {
613+
val mat1 = Matrix[Double](NArray(1.0, 2.0, 3.0, 4.0, 5.0, 6.0), 2, 3)
614+
val mat2 = Matrix[Double](NArray(10.0, 20.0, 30.0, 40.0, 50.0, 60.0), 3, 2)
615+
616+
// Transpose mat2 to have the same shape as mat1
617+
val mat2T = mat2.transpose
618+
619+
assert(mat1.rows == mat2T.rows)
620+
assert(mat1.cols == mat2T.cols)
621+
622+
val result = mat1.hadamard(mat2T)
623+
624+
assert(result.rows == 2)
625+
assert(result.cols == 3)
626+
// mat1 (col-major 2x3): [[1,3,5], [2,4,6]]
627+
// mat2 (col-major 3x2): [[10,40], [20,50], [30,60]]
628+
// mat2T (row-major 2x3): [[10,20,30], [40,50,60]]
629+
assertEqualsDouble(result(0, 0), 1.0 * 10.0, 0.0001) // 10
630+
assertEqualsDouble(result(1, 0), 2.0 * 40.0, 0.0001) // 80
631+
assertEqualsDouble(result(0, 1), 3.0 * 20.0, 0.0001) // 60
632+
assertEqualsDouble(result(1, 1), 4.0 * 50.0, 0.0001) // 200
633+
assertEqualsDouble(result(0, 2), 5.0 * 30.0, 0.0001) // 150
634+
assertEqualsDouble(result(1, 2), 6.0 * 60.0, 0.0001) // 360
635+
}
636+
568637
test("map rows") {
569638
val mapped = mat1to9.mapRows[Double](row => row * 2)
570639
assertVecEquals[Double](mapped.raw, mat1to9.raw * 2)

vecxt/test/src/notImpl.test.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ class NotImplTest extends FunSuite:
2222
intercept[NotImplementedError](m / 2.0)
2323
}
2424

25-
test("DoubleMatrix.hadamard with non-simple layout throws") {
26-
intercept[NotImplementedError](m.hadamard(m))
27-
}
2825
test("DoubleMatrix./:/ with non-simple layout throws") {
2926
intercept[NotImplementedError](m./:/(m))
3027
}

0 commit comments

Comments
 (0)