From b16610071db608ef8a827ed6767c17aae5681bba Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Wed, 4 Feb 2026 17:38:03 +0300 Subject: [PATCH 01/15] Add mxm --- QuadTree.Tests/Tests.LinearAlgebra.fs | 117 ++++++++++++++++++++++++++ QuadTree/LinearAlgebra.fs | 116 +++++++++++++++++++++++++ 2 files changed, 233 insertions(+) diff --git a/QuadTree.Tests/Tests.LinearAlgebra.fs b/QuadTree.Tests/Tests.LinearAlgebra.fs index e285644..a0eeb6b 100644 --- a/QuadTree.Tests/Tests.LinearAlgebra.fs +++ b/QuadTree.Tests/Tests.LinearAlgebra.fs @@ -340,3 +340,120 @@ let ``Simple vxm. 3 * (3x5)`` () = let actual = LinearAlgebra.vxm op_add op_mult v m Assert.Equal(expected, actual) + +[] +let ``Simple mxm`` () = + // 222D + // 222D + // 222D + // DDDD + let tree = + qtree.Node( + qtree.Leaf << UserValue <| Some 2, + qtree.Node( + qtree.Leaf << UserValue <| Some 2, + qtree.Leaf Dummy, + qtree.Leaf << UserValue <| Some 2, + qtree.Leaf Dummy + ), + qtree.Node( + qtree.Leaf << UserValue <| Some 2, + qtree.Leaf << UserValue <| Some 2, + qtree.Leaf Dummy, + qtree.Leaf Dummy + ), + qtree.Node(qtree.Leaf << UserValue <| Some 2, qtree.Leaf Dummy, qtree.Leaf Dummy, qtree.Leaf Dummy) + ) + + let tree_expected = + qtree.Node( + qtree.Leaf << UserValue <| Some 12, + qtree.Node( + qtree.Leaf << UserValue <| Some 12, + qtree.Leaf Dummy, + qtree.Leaf << UserValue <| Some 12, + qtree.Leaf Dummy + ), + qtree.Node( + qtree.Leaf << UserValue <| Some 12, + qtree.Leaf << UserValue <| Some 12, + qtree.Leaf Dummy, + qtree.Leaf Dummy + ), + qtree.Node(qtree.Leaf << UserValue <| Some 12, qtree.Leaf Dummy, qtree.Leaf Dummy, qtree.Leaf Dummy) + ) + + let m1 = + SparseMatrix(3UL, 3UL, 9UL, Matrix.Storage(4UL, tree)) + + let m2 = m1 + + let expected = + SparseMatrix(3UL, 3UL, 9UL, Matrix.Storage(4UL, tree_expected)) + + let op_add o1 o2 = + match o1, o2 with + | Some x, Some y -> Some <| x + y + | Some x, None + | None, Some x -> Some x + | None, None -> None + + let op_mult o1 o2 = + match o1, o2 with + | Some x, Some y -> Some <| x * y + | _ -> None + + let actual = + match LinearAlgebra.mxm op_add op_mult m1 m2 with + | Result.Success m -> m + | _ -> failwith "Unreachable" + + Assert.Equal(expected.storage.data, actual.storage.data) + +[] +let ``Sparse mxm`` () = + let m1 = + let d = + [ 0UL, 0UL, 1 + 1UL, 1UL, 2 + 2UL, 2UL, 3 ] + + let clist = Matrix.CoordinateList(3UL, 3UL, d) + Matrix.fromCoordinateList clist + + let m2 = + let d = + [ 0UL, 0UL, 3 + 1UL, 1UL, 2 + 2UL, 2UL, 1 ] + + let clist = Matrix.CoordinateList(3UL, 3UL, d) + Matrix.fromCoordinateList clist + + let expected = + let d = + [ 0UL, 0UL, 3 + 1UL, 1UL, 4 + 2UL, 2UL, 3 ] + + let clist = Matrix.CoordinateList(3UL, 3UL, d) + Matrix.fromCoordinateList clist + + let op_add o1 o2 = + match o1, o2 with + | Some x, Some y -> Some <| x + y + | Some x, None + | None, Some x -> Some x + | None, None -> None + + let op_mult o1 o2 = + match o1, o2 with + | Some x, Some y -> Some <| x * y + | _ -> None + + let actual = + match LinearAlgebra.mxm op_add op_mult m1 m2 with + | Result.Success m -> m + | Result.Failure e -> failwith (e.ToString()) + + Assert.Equal(expected, actual) diff --git a/QuadTree/LinearAlgebra.fs b/QuadTree/LinearAlgebra.fs index 618ef52..cbb927a 100644 --- a/QuadTree/LinearAlgebra.fs +++ b/QuadTree/LinearAlgebra.fs @@ -7,6 +7,7 @@ type Error<'value1, 'value2, 'value3> = | InconsistentSizeOfArguments of Vector.SparseVector<'value1> * Matrix.SparseMatrix<'value2> | VectorAdditionProblem of Vector.Error<'value3, 'value3> + let rec multScalar op_add (x: uint64) y = if x = 1UL then y @@ -99,3 +100,118 @@ let vxm op_add op_mult (vector: Vector.SparseVector<'a>) (matrix: Matrix.SparseM |> Result.Success else (Error.InconsistentSizeOfArguments(vector, matrix)) |> Result.Failure + +let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'b>) = + let rec multiply size m1 m2 = + let divided (nw1, ne1, sw1, se1) (nw2, ne2, sw2, se2) = + let halfSize = size / 2UL + + // Double check this code + let nw1xnw2, ne1xsw2, nw1xne2, ne1xse2, sw1xnw2, se1xsw2, sw1xne2, se1xse2 = + (multiply halfSize nw1 nw2), + (multiply halfSize ne1 sw2), + (multiply halfSize nw1 ne2), + (multiply halfSize ne1 se2), + (multiply halfSize sw1 nw2), + (multiply halfSize se1 sw2), + (multiply halfSize sw1 ne2), + (multiply halfSize se1 se2) + + match nw1xnw2, ne1xsw2, nw1xne2, ne1xse2, sw1xnw2, se1xsw2, sw1xne2, se1xse2 with + | Result.Success(tnw1xnw2, nvals_nw1xnw2), + Result.Success(tne1xsw2, nvals_ne1xsw2), + Result.Success(tnw1xne2, nvals_nw1xne2), + Result.Success(tne1xse2, nvals_ne1xse2), + Result.Success(tsw1xnw2, nvals_sw1xnw2), + Result.Success(tse1xsw2, nvals_se1xsw2), + Result.Success(tsw1xne2, nvals_sw1xne2), + Result.Success(tse1xse2, nvals_se1xse2) -> + let nrows = halfSize * 1UL + let ncols = halfSize * 1UL + let storageSize = halfSize * 1UL + + let nw1xnw2 = + Matrix.SparseMatrix(nrows, ncols, nvals_nw1xnw2, Matrix.Storage(storageSize, tnw1xnw2)) + + let ne1xsw2 = + Matrix.SparseMatrix(nrows, ncols, nvals_ne1xsw2, Matrix.Storage(storageSize, tne1xsw2)) + + let nw1xne2 = + Matrix.SparseMatrix(nrows, ncols, nvals_nw1xne2, Matrix.Storage(storageSize, tnw1xne2)) + + let ne1xse2 = + Matrix.SparseMatrix(nrows, ncols, nvals_ne1xse2, Matrix.Storage(storageSize, tne1xse2)) + + let sw1xnw2 = + Matrix.SparseMatrix(nrows, ncols, nvals_sw1xnw2, Matrix.Storage(storageSize, tsw1xnw2)) + + let se1xsw2 = + Matrix.SparseMatrix(nrows, ncols, nvals_se1xsw2, Matrix.Storage(storageSize, tse1xsw2)) + + let sw1xne2 = + Matrix.SparseMatrix(nrows, ncols, nvals_sw1xne2, Matrix.Storage(storageSize, tsw1xne2)) + + let se1xse2 = + Matrix.SparseMatrix(nrows, ncols, nvals_se1xse2, Matrix.Storage(storageSize, tse1xse2)) + + let mAdd m1 (m2: Matrix.SparseMatrix<_>) = + match m2.storage.data with + | Matrix.qtree.Leaf Dummy -> Result.Success m1 + | _ -> Matrix.map2 m1 m2 op_add + + let rnw = mAdd nw1xnw2 ne1xsw2 + let rne = mAdd nw1xne2 ne1xse2 + let rsw = mAdd sw1xnw2 se1xsw2 + let rse = mAdd sw1xne2 se1xse2 + + match rnw, rne, rsw, rse with + | Result.Success(nw), Result.Success(ne), Result.Success(sw), Result.Success(se) -> + Result.Success( + Matrix.mkNode nw.storage.data ne.storage.data sw.storage.data se.storage.data, + nw.nvals + ne.nvals + sw.nvals + se.nvals + ) + | Result.Failure(e), _, _, _ + | _, Result.Failure(e), _, _ + | _, _, Result.Failure(e), _ + | _, _, _, Result.Failure(e) -> Result.Failure(e) + + | Result.Failure(e), _, _, _, _, _, _, _ + | _, Result.Failure(e), _, _, _, _, _, _ + | _, _, Result.Failure(e), _, _, _, _, _ + | _, _, _, Result.Failure(e), _, _, _, _ + | _, _, _, _, Result.Failure(e), _, _, _ + | _, _, _, _, _, Result.Failure(e), _, _ + | _, _, _, _, _, _, Result.Failure(e), _ + | _, _, _, _, _, _, _, Result.Failure(e) -> Result.Failure(e) + + match m1, m2 with + | Matrix.qtree.Leaf(UserValue v1), Matrix.qtree.Leaf(UserValue v2) -> + let res = multScalar op_add (uint64 size) (op_mult v1 v2) + + let nnz = + match res with + | None -> 0UL + | _ -> size * size * 1UL + + Result.Success(Matrix.qtree.Leaf(UserValue res), nnz) + | Matrix.qtree.Leaf(UserValue(_)), Matrix.qtree.Node(nw2, ne2, sw2, se2) -> + divided (m1, m1, m1, m1) (nw2, ne2, sw2, se2) + | Matrix.qtree.Node(nw1, ne1, sw1, se1), Matrix.qtree.Leaf(UserValue(_)) -> + divided (nw1, ne1, sw1, se1) (m2, m2, m2, m2) + | Matrix.qtree.Node(nw1, ne1, sw1, se1), Matrix.qtree.Node(nw2, ne2, sw2, se2) -> + divided (nw1, ne1, sw1, se1) (nw2, ne2, sw2, se2) + | Matrix.qtree.Leaf Dummy, _ + | _, Matrix.qtree.Leaf Dummy -> Result.Success(Matrix.qtree.Leaf Dummy, 0UL) + | _ -> Result.Failure(Matrix.Error.InconsistentStructureOfStorages(m1, m2)) + + if uint64 m1.ncols = uint64 m2.nrows then + let nrows = m1.nrows + let ncols = m2.ncols + let storageSize = m1.storage.size + + match multiply (uint64 storageSize) m1.storage.data m2.storage.data with + | Result.Success(tree, nvals) -> + Result.Success(Matrix.SparseMatrix(nrows, ncols, nvals, Matrix.Storage(storageSize, tree))) + | Result.Failure(e) -> Result.Failure(e) + else + Matrix.Error.InconsistentSizeOfArguments(m1, m2) |> Result.Failure From 0d141d5c06e3c2be07bd26bfa4ddfb55066e3171 Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Wed, 4 Feb 2026 18:50:09 +0300 Subject: [PATCH 02/15] Add expanding and shrinking mxm tests --- QuadTree.Tests/Tests.LinearAlgebra.fs | 178 ++++++++++++++++---------- QuadTree/LinearAlgebra.fs | 31 ++++- 2 files changed, 135 insertions(+), 74 deletions(-) diff --git a/QuadTree.Tests/Tests.LinearAlgebra.fs b/QuadTree.Tests/Tests.LinearAlgebra.fs index a0eeb6b..a6e0bb8 100644 --- a/QuadTree.Tests/Tests.LinearAlgebra.fs +++ b/QuadTree.Tests/Tests.LinearAlgebra.fs @@ -17,6 +17,20 @@ N,N,3,N = 6,6,14,10 *) + +let op_add x y = + match (x, y) with + | Some(a), Some(b) -> Some(a + b) + | Some(a), _ + | _, Some(a) -> Some(a) + | _ -> None + +let op_mult x y = + match (x, y) with + | Some(a), Some(b) -> Some(a * b) + | _ -> None + + [] let ``Simple vxm. All sizes are power of two.`` () = let m = @@ -52,18 +66,6 @@ let ``Simple vxm. All sizes are power of two.`` () = let store = Vector.Storage(4UL, tree) SparseVector(4UL, 4UL, store) - let op_add x y = - match (x, y) with - | Some(a), Some(b) -> Some(a + b) - | Some(a), _ - | _, Some(a) -> Some(a) - | _ -> None - - let op_mult x y = - match (x, y) with - | Some(a), Some(b) -> Some(a * b) - | _ -> None - let expected = let tree = Vector.btree.Node( @@ -132,18 +134,6 @@ let ``Simple vxm. 3 * (3x4)`` () = let store = Vector.Storage(4UL, tree) SparseVector(3UL, 3UL, store) - let op_add x y = - match (x, y) with - | Some(a), Some(b) -> Some(a + b) - | Some(a), _ - | _, Some(a) -> Some(a) - | _ -> None - - let op_mult x y = - match (x, y) with - | Some(a), Some(b) -> Some(a * b) - | _ -> None - let expected = let tree = Vector.btree.Node( @@ -204,17 +194,6 @@ let ``Simple vxm. 4 * (4x3).`` () = let store = Vector.Storage(4UL, tree) SparseVector(4UL, 4UL, store) - let op_add x y = - match (x, y) with - | Some(a), Some(b) -> Some(a + b) - | Some(a), _ - | _, Some(a) -> Some(a) - | _ -> None - - let op_mult x y = - match (x, y) with - | Some(a), Some(b) -> Some(a * b) - | _ -> None let expected = let tree = @@ -309,18 +288,6 @@ let ``Simple vxm. 3 * (3x5)`` () = let store = Vector.Storage(4UL, tree) SparseVector(3UL, 3UL, store) - let op_add x y = - match (x, y) with - | Some(a), Some(b) -> Some(a + b) - | Some(a), _ - | _, Some(a) -> Some(a) - | _ -> None - - let op_mult x y = - match (x, y) with - | Some(a), Some(b) -> Some(a * b) - | _ -> None - let expected = let tree = Vector.btree.Node( @@ -391,18 +358,6 @@ let ``Simple mxm`` () = let expected = SparseMatrix(3UL, 3UL, 9UL, Matrix.Storage(4UL, tree_expected)) - let op_add o1 o2 = - match o1, o2 with - | Some x, Some y -> Some <| x + y - | Some x, None - | None, Some x -> Some x - | None, None -> None - - let op_mult o1 o2 = - match o1, o2 with - | Some x, Some y -> Some <| x * y - | _ -> None - let actual = match LinearAlgebra.mxm op_add op_mult m1 m2 with | Result.Success m -> m @@ -439,17 +394,100 @@ let ``Sparse mxm`` () = let clist = Matrix.CoordinateList(3UL, 3UL, d) Matrix.fromCoordinateList clist - let op_add o1 o2 = - match o1, o2 with - | Some x, Some y -> Some <| x + y - | Some x, None - | None, Some x -> Some x - | None, None -> None - - let op_mult o1 o2 = - match o1, o2 with - | Some x, Some y -> Some <| x * y - | _ -> None + let actual = + match LinearAlgebra.mxm op_add op_mult m1 m2 with + | Result.Success m -> m + | Result.Failure e -> failwith (e.ToString()) + + Assert.Equal(expected, actual) + +[] +let ``Shrinking mxm`` () = + // 2 x 3 + // 1 0 2 + // 0 3 0 + let m1 = + let d = + [ 0UL, 0UL, 1 + 0UL, 2UL, 2 + 1UL, 1UL, 3 ] + + let clist = Matrix.CoordinateList(2UL, 3UL, d) + Matrix.fromCoordinateList clist + + // 3 x 2 + // 0 4 + // 5 0 + // 6 0 + let m2 = + let d = + [ 0UL, 1UL, 4 + 1UL, 0UL, 5 + 2UL, 0UL, 6 ] + + let clist = Matrix.CoordinateList(3UL, 2UL, d) + Matrix.fromCoordinateList clist + + // 2 x 2 + // 12 4 + // 15 0 + let expected = + let d = + [ 0UL, 0UL, 12 + 0UL, 1UL, 4 + 1UL, 0UL, 15 ] + + let clist = Matrix.CoordinateList(2UL, 2UL, d) + Matrix.fromCoordinateList clist + + let actual = + match LinearAlgebra.mxm op_add op_mult m1 m2 with + | Result.Success m -> m + | Result.Failure e -> failwith (e.ToString()) + + Assert.Equal(expected, actual) + +[] +let ``Expanding mxm`` () = + // 3 x 2 + // 1 0 + // 0 2 + // 3 0 + let m1 = + let d = + [ 0UL, 0UL, 1 + 1UL, 1UL, 2 + 2UL, 0UL, 3 ] + + let clist = Matrix.CoordinateList(3UL, 2UL, d) + Matrix.fromCoordinateList clist + // 2 x 3 + // 4 5 6 + // 0 0 0 + let m2 = + let d = + [ 0UL, 0UL, 4 + 0UL, 1UL, 5 + 0UL, 2UL, 6 ] + + let clist = Matrix.CoordinateList(2UL, 3UL, d) + Matrix.fromCoordinateList clist + + // 3 x 3 + // 4 5 6 + // 0 0 0 + // 12 15 18 + let expected = + let d = + [ 0UL, 0UL, 4 + 0UL, 1UL, 5 + 0UL, 2UL, 6 + 2UL, 0UL, 12 + 2UL, 1UL, 15 + 2UL, 2UL, 18 ] + + let clist = Matrix.CoordinateList(3UL, 3UL, d) + Matrix.fromCoordinateList clist let actual = match LinearAlgebra.mxm op_add op_mult m1 m2 with diff --git a/QuadTree/LinearAlgebra.fs b/QuadTree/LinearAlgebra.fs index cbb927a..d58e9d1 100644 --- a/QuadTree/LinearAlgebra.fs +++ b/QuadTree/LinearAlgebra.fs @@ -101,7 +101,20 @@ let vxm op_add op_mult (vector: Vector.SparseVector<'a>) (matrix: Matrix.SparseM else (Error.InconsistentSizeOfArguments(vector, matrix)) |> Result.Failure -let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'b>) = +let rec shrink tree (size: uint64) = + match tree with + | Matrix.qtree.Node(nw, ne, sw, se) when ne = sw && ne = Matrix.qtree.Leaf Dummy -> shrink nw (size / 2UL) + | _ -> tree, size + +let rec expand tree expandRatio = + match expandRatio with + | 1UL -> tree + | _ -> + expand + (Matrix.mkNode tree (Matrix.qtree.Leaf Dummy) (Matrix.qtree.Leaf Dummy) (Matrix.qtree.Leaf Dummy)) + (expandRatio / 2UL) + +let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'a>) = let rec multiply size m1 m2 = let divided (nw1, ne1, sw1, se1) (nw2, ne2, sw2, se2) = let halfSize = size / 2UL @@ -202,15 +215,25 @@ let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'b divided (nw1, ne1, sw1, se1) (nw2, ne2, sw2, se2) | Matrix.qtree.Leaf Dummy, _ | _, Matrix.qtree.Leaf Dummy -> Result.Success(Matrix.qtree.Leaf Dummy, 0UL) - | _ -> Result.Failure(Matrix.Error.InconsistentStructureOfStorages(m1, m2)) if uint64 m1.ncols = uint64 m2.nrows then let nrows = m1.nrows let ncols = m2.ncols - let storageSize = m1.storage.size + let storageSize = max m1.storage.size m2.storage.size - match multiply (uint64 storageSize) m1.storage.data m2.storage.data with + let m1_tree, m2_tree = + if m1.storage.size < m2.storage.size then + expand (m1.storage.data) (m2.storage.size / (uint64 m1.storage.size)), m2.storage.data + else if m1.storage.size > m2.storage.size then + m1.storage.data, expand (m2.storage.data) (m1.storage.size / (uint64 m2.storage.size)) + else + m1.storage.data, m2.storage.data + + match multiply (uint64 storageSize) m1_tree m2_tree with | Result.Success(tree, nvals) -> + // in case the resulting storageSize can be smaller + // e.g. (2x3) * (3x2) matrices + let tree, storageSize = shrink tree storageSize Result.Success(Matrix.SparseMatrix(nrows, ncols, nvals, Matrix.Storage(storageSize, tree))) | Result.Failure(e) -> Result.Failure(e) else From 7b443f3876ff4d90b27bef044c7d64d429bff513 Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Thu, 5 Feb 2026 10:35:49 +0300 Subject: [PATCH 03/15] Move helper functions into inner block --- QuadTree/LinearAlgebra.fs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/QuadTree/LinearAlgebra.fs b/QuadTree/LinearAlgebra.fs index d58e9d1..a63a1f2 100644 --- a/QuadTree/LinearAlgebra.fs +++ b/QuadTree/LinearAlgebra.fs @@ -101,20 +101,23 @@ let vxm op_add op_mult (vector: Vector.SparseVector<'a>) (matrix: Matrix.SparseM else (Error.InconsistentSizeOfArguments(vector, matrix)) |> Result.Failure -let rec shrink tree (size: uint64) = - match tree with - | Matrix.qtree.Node(nw, ne, sw, se) when ne = sw && ne = Matrix.qtree.Leaf Dummy -> shrink nw (size / 2UL) - | _ -> tree, size - -let rec expand tree expandRatio = - match expandRatio with - | 1UL -> tree - | _ -> - expand - (Matrix.mkNode tree (Matrix.qtree.Leaf Dummy) (Matrix.qtree.Leaf Dummy) (Matrix.qtree.Leaf Dummy)) - (expandRatio / 2UL) + let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'a>) = + + let rec shrink tree (size: uint64) = + match tree with + | Matrix.qtree.Node(nw, ne, sw, _) when ne = sw && ne = Matrix.qtree.Leaf Dummy -> shrink nw (size / 2UL) + | _ -> tree, size + + let rec expand tree expandRatio = + match expandRatio with + | 1UL -> tree + | _ -> + expand + (Matrix.mkNode tree (Matrix.qtree.Leaf Dummy) (Matrix.qtree.Leaf Dummy) (Matrix.qtree.Leaf Dummy)) + (expandRatio / 2UL) + let rec multiply size m1 m2 = let divided (nw1, ne1, sw1, se1) (nw2, ne2, sw2, se2) = let halfSize = size / 2UL From c431f1cf7062390f2457c3e8976ef53107000e2d Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Thu, 5 Feb 2026 10:45:34 +0300 Subject: [PATCH 04/15] Make mxm more generic --- QuadTree/LinearAlgebra.fs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/QuadTree/LinearAlgebra.fs b/QuadTree/LinearAlgebra.fs index a63a1f2..3675d6f 100644 --- a/QuadTree/LinearAlgebra.fs +++ b/QuadTree/LinearAlgebra.fs @@ -102,8 +102,12 @@ let vxm op_add op_mult (vector: Vector.SparseVector<'a>) (matrix: Matrix.SparseM (Error.InconsistentSizeOfArguments(vector, matrix)) |> Result.Failure +type MXMError<'value1, 'value2, 'value3> = + | InconsistentSizeOfArguments of Matrix.SparseMatrix<'value1> * Matrix.SparseMatrix<'value2> + | MatrixAdditionProblem of Matrix.Error<'value3, 'value3> -let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'a>) = + +let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'b>) = let rec shrink tree (size: uint64) = match tree with @@ -189,7 +193,7 @@ let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'a | Result.Failure(e), _, _, _ | _, Result.Failure(e), _, _ | _, _, Result.Failure(e), _ - | _, _, _, Result.Failure(e) -> Result.Failure(e) + | _, _, _, Result.Failure(e) -> Result.Failure(MXMError.MatrixAdditionProblem(e)) | Result.Failure(e), _, _, _, _, _, _, _ | _, Result.Failure(e), _, _, _, _, _, _ @@ -240,4 +244,4 @@ let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'a Result.Success(Matrix.SparseMatrix(nrows, ncols, nvals, Matrix.Storage(storageSize, tree))) | Result.Failure(e) -> Result.Failure(e) else - Matrix.Error.InconsistentSizeOfArguments(m1, m2) |> Result.Failure + MXMError.InconsistentSizeOfArguments(m1, m2) |> Result.Failure From 3a039e62d0245a856b8b160c9a752727f360a68a Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Thu, 5 Feb 2026 10:54:43 +0300 Subject: [PATCH 05/15] Add leaf instantiation helper functions --- QuadTree.Tests/Tests.LinearAlgebra.fs | 145 +++++--------------------- 1 file changed, 26 insertions(+), 119 deletions(-) diff --git a/QuadTree.Tests/Tests.LinearAlgebra.fs b/QuadTree.Tests/Tests.LinearAlgebra.fs index a6e0bb8..c03bb8d 100644 --- a/QuadTree.Tests/Tests.LinearAlgebra.fs +++ b/QuadTree.Tests/Tests.LinearAlgebra.fs @@ -30,31 +30,18 @@ let op_mult x y = | Some(a), Some(b) -> Some(a * b) | _ -> None +let leaf_v v = qtree.Leaf << UserValue <| Some v +let leaf_n () = qtree.Leaf << UserValue <| None [] let ``Simple vxm. All sizes are power of two.`` () = let m = let tree = Matrix.qtree.Node( - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(UserValue(Some(2))) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(UserValue(Some(3))) - ), + Matrix.qtree.Node(leaf_n (), leaf_v 1, leaf_v 3, leaf_v 2), + Matrix.qtree.Node(leaf_v 1, leaf_n (), leaf_v 2, leaf_v 3), Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(UserValue(None)) - ) + Matrix.qtree.Node(leaf_v 1, leaf_v 2, leaf_v 3, leaf_n ()) ) let store = Matrix.Storage(4UL, tree) @@ -95,30 +82,10 @@ let ``Simple vxm. 3 * (3x4)`` () = let m = let tree = Matrix.qtree.Node( - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(UserValue(Some(2))) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(UserValue(Some(3))) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy) - ) + Matrix.qtree.Node(leaf_n (), leaf_v 1, leaf_v 3, leaf_v 2), + Matrix.qtree.Node(leaf_v 1, leaf_n (), leaf_v 2, leaf_v 3), + Matrix.qtree.Node(leaf_n (), leaf_n (), Matrix.qtree.Leaf(Dummy), Matrix.qtree.Leaf(Dummy)), + Matrix.qtree.Node(leaf_v 1, leaf_v 2, Matrix.qtree.Leaf(Dummy), Matrix.qtree.Leaf(Dummy)) ) let store = Matrix.Storage(4UL, tree) @@ -164,25 +131,10 @@ let ``Simple vxm. 4 * (4x3).`` () = let m = let tree = Matrix.qtree.Node( - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(UserValue(Some(2))) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(Dummy) - ), + Matrix.qtree.Node(leaf_n (), leaf_v 1, leaf_v 3, leaf_v 2), + Matrix.qtree.Node(leaf_v 1, Matrix.qtree.Leaf(Dummy), leaf_v 2, Matrix.qtree.Leaf(Dummy)), Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(Dummy) - ) + Matrix.qtree.Node(leaf_v 1, Matrix.qtree.Leaf(Dummy), leaf_v 3, Matrix.qtree.Leaf(Dummy)) ) let store = Matrix.Storage(4UL, tree) @@ -230,41 +182,16 @@ let ``Simple vxm. 3 * (3x5)`` () = let tree = Matrix.qtree.Node( Matrix.qtree.Node( - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(UserValue(Some(2))) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(UserValue(Some(3))) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy) - ) + Matrix.qtree.Node(leaf_n (), leaf_v 1, leaf_v 3, leaf_v 2), + Matrix.qtree.Node(leaf_v 1, leaf_n (), leaf_v 2, leaf_v 3), + Matrix.qtree.Node(leaf_n (), leaf_n (), Matrix.qtree.Leaf(Dummy), Matrix.qtree.Leaf(Dummy)), + Matrix.qtree.Node(leaf_v 1, leaf_v 2, Matrix.qtree.Leaf(Dummy), Matrix.qtree.Leaf(Dummy)) ), Matrix.qtree.Node( - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(Dummy) - ), + Matrix.qtree.Node(leaf_n (), Matrix.qtree.Leaf(Dummy), leaf_v 1, Matrix.qtree.Leaf(Dummy)), Matrix.qtree.Leaf(Dummy), Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), + leaf_n (), Matrix.qtree.Leaf(Dummy), Matrix.qtree.Leaf(Dummy), Matrix.qtree.Leaf(Dummy) @@ -316,38 +243,18 @@ let ``Simple mxm`` () = // DDDD let tree = qtree.Node( - qtree.Leaf << UserValue <| Some 2, - qtree.Node( - qtree.Leaf << UserValue <| Some 2, - qtree.Leaf Dummy, - qtree.Leaf << UserValue <| Some 2, - qtree.Leaf Dummy - ), - qtree.Node( - qtree.Leaf << UserValue <| Some 2, - qtree.Leaf << UserValue <| Some 2, - qtree.Leaf Dummy, - qtree.Leaf Dummy - ), - qtree.Node(qtree.Leaf << UserValue <| Some 2, qtree.Leaf Dummy, qtree.Leaf Dummy, qtree.Leaf Dummy) + leaf_v 2, + qtree.Node(leaf_v 2, qtree.Leaf Dummy, leaf_v 2, qtree.Leaf Dummy), + qtree.Node(leaf_v 2, leaf_v 2, qtree.Leaf Dummy, qtree.Leaf Dummy), + qtree.Node(leaf_v 2, qtree.Leaf Dummy, qtree.Leaf Dummy, qtree.Leaf Dummy) ) let tree_expected = qtree.Node( - qtree.Leaf << UserValue <| Some 12, - qtree.Node( - qtree.Leaf << UserValue <| Some 12, - qtree.Leaf Dummy, - qtree.Leaf << UserValue <| Some 12, - qtree.Leaf Dummy - ), - qtree.Node( - qtree.Leaf << UserValue <| Some 12, - qtree.Leaf << UserValue <| Some 12, - qtree.Leaf Dummy, - qtree.Leaf Dummy - ), - qtree.Node(qtree.Leaf << UserValue <| Some 12, qtree.Leaf Dummy, qtree.Leaf Dummy, qtree.Leaf Dummy) + leaf_v 12, + qtree.Node(leaf_v 12, qtree.Leaf Dummy, leaf_v 12, qtree.Leaf Dummy), + qtree.Node(leaf_v 12, leaf_v 12, qtree.Leaf Dummy, qtree.Leaf Dummy), + qtree.Node(leaf_v 12, qtree.Leaf Dummy, qtree.Leaf Dummy, qtree.Leaf Dummy) ) let m1 = From cd11138c2671a0a06c32ab92c7344335656a1edf Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Thu, 5 Feb 2026 10:55:29 +0300 Subject: [PATCH 06/15] else if -> elif --- QuadTree/LinearAlgebra.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QuadTree/LinearAlgebra.fs b/QuadTree/LinearAlgebra.fs index 3675d6f..3406d81 100644 --- a/QuadTree/LinearAlgebra.fs +++ b/QuadTree/LinearAlgebra.fs @@ -231,7 +231,7 @@ let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'b let m1_tree, m2_tree = if m1.storage.size < m2.storage.size then expand (m1.storage.data) (m2.storage.size / (uint64 m1.storage.size)), m2.storage.data - else if m1.storage.size > m2.storage.size then + elif m1.storage.size > m2.storage.size then m1.storage.data, expand (m2.storage.data) (m1.storage.size / (uint64 m2.storage.size)) else m1.storage.data, m2.storage.data From ecca8cbbdbd28eb3f8c08855df5e9a3004004746 Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Thu, 5 Feb 2026 11:00:00 +0300 Subject: [PATCH 07/15] Use storageSize inside mxm --- QuadTree/LinearAlgebra.fs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/QuadTree/LinearAlgebra.fs b/QuadTree/LinearAlgebra.fs index 3406d81..e54485e 100644 --- a/QuadTree/LinearAlgebra.fs +++ b/QuadTree/LinearAlgebra.fs @@ -122,7 +122,7 @@ let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'b (Matrix.mkNode tree (Matrix.qtree.Leaf Dummy) (Matrix.qtree.Leaf Dummy) (Matrix.qtree.Leaf Dummy)) (expandRatio / 2UL) - let rec multiply size m1 m2 = + let rec multiply (size: uint64) m1 m2 = let divided (nw1, ne1, sw1, se1) (nw2, ne2, sw2, se2) = let halfSize = size / 2UL @@ -146,9 +146,9 @@ let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'b Result.Success(tse1xsw2, nvals_se1xsw2), Result.Success(tsw1xne2, nvals_sw1xne2), Result.Success(tse1xse2, nvals_se1xse2) -> - let nrows = halfSize * 1UL - let ncols = halfSize * 1UL - let storageSize = halfSize * 1UL + let nrows = (uint64 halfSize) * 1UL + let ncols = (uint64 halfSize) * 1UL + let storageSize = halfSize let nw1xnw2 = Matrix.SparseMatrix(nrows, ncols, nvals_nw1xnw2, Matrix.Storage(storageSize, tnw1xnw2)) @@ -211,7 +211,7 @@ let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'b let nnz = match res with | None -> 0UL - | _ -> size * size * 1UL + | _ -> (uint64 <| size * size) * 1UL Result.Success(Matrix.qtree.Leaf(UserValue res), nnz) | Matrix.qtree.Leaf(UserValue(_)), Matrix.qtree.Node(nw2, ne2, sw2, se2) -> @@ -236,7 +236,7 @@ let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'b else m1.storage.data, m2.storage.data - match multiply (uint64 storageSize) m1_tree m2_tree with + match multiply storageSize m1_tree m2_tree with | Result.Success(tree, nvals) -> // in case the resulting storageSize can be smaller // e.g. (2x3) * (3x2) matrices From cad5fac4eec7cf1cda63adb746d1aadc9399609a Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Thu, 5 Feb 2026 11:04:52 +0300 Subject: [PATCH 08/15] nw1xnw2 -> nw1_x_nw2 --- QuadTree/LinearAlgebra.fs | 60 +++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/QuadTree/LinearAlgebra.fs b/QuadTree/LinearAlgebra.fs index e54485e..ab4cba6 100644 --- a/QuadTree/LinearAlgebra.fs +++ b/QuadTree/LinearAlgebra.fs @@ -127,7 +127,7 @@ let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'b let halfSize = size / 2UL // Double check this code - let nw1xnw2, ne1xsw2, nw1xne2, ne1xse2, sw1xnw2, se1xsw2, sw1xne2, se1xse2 = + let nw1_x_nw2, ne1_x_sw2, nw1_x_ne2, ne1_x_se2, sw1_x_nw2, se1_x_sw2, sw1_x_ne2, se1_x_se2 = (multiply halfSize nw1 nw2), (multiply halfSize ne1 sw2), (multiply halfSize nw1 ne2), @@ -137,52 +137,52 @@ let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'b (multiply halfSize sw1 ne2), (multiply halfSize se1 se2) - match nw1xnw2, ne1xsw2, nw1xne2, ne1xse2, sw1xnw2, se1xsw2, sw1xne2, se1xse2 with - | Result.Success(tnw1xnw2, nvals_nw1xnw2), - Result.Success(tne1xsw2, nvals_ne1xsw2), - Result.Success(tnw1xne2, nvals_nw1xne2), - Result.Success(tne1xse2, nvals_ne1xse2), - Result.Success(tsw1xnw2, nvals_sw1xnw2), - Result.Success(tse1xsw2, nvals_se1xsw2), - Result.Success(tsw1xne2, nvals_sw1xne2), - Result.Success(tse1xse2, nvals_se1xse2) -> + match nw1_x_nw2, ne1_x_sw2, nw1_x_ne2, ne1_x_se2, sw1_x_nw2, se1_x_sw2, sw1_x_ne2, se1_x_se2 with + | Result.Success(tnw1_x_nw2, nvals_nw1_x_nw2), + Result.Success(tne1_x_sw2, nvals_ne1_x_sw2), + Result.Success(tnw1_x_ne2, nvals_nw1_x_ne2), + Result.Success(tne1_x_se2, nvals_ne1_x_se2), + Result.Success(tsw1_x_nw2, nvals_sw1_x_nw2), + Result.Success(tse1_x_sw2, nvals_se1_x_sw2), + Result.Success(tsw1_x_ne2, nvals_sw1_x_ne2), + Result.Success(tse1_x_se2, nvals_se1_x_se2) -> let nrows = (uint64 halfSize) * 1UL let ncols = (uint64 halfSize) * 1UL let storageSize = halfSize - let nw1xnw2 = - Matrix.SparseMatrix(nrows, ncols, nvals_nw1xnw2, Matrix.Storage(storageSize, tnw1xnw2)) + let nw1_x_nw2 = + Matrix.SparseMatrix(nrows, ncols, nvals_nw1_x_nw2, Matrix.Storage(storageSize, tnw1_x_nw2)) - let ne1xsw2 = - Matrix.SparseMatrix(nrows, ncols, nvals_ne1xsw2, Matrix.Storage(storageSize, tne1xsw2)) + let ne1_x_sw2 = + Matrix.SparseMatrix(nrows, ncols, nvals_ne1_x_sw2, Matrix.Storage(storageSize, tne1_x_sw2)) - let nw1xne2 = - Matrix.SparseMatrix(nrows, ncols, nvals_nw1xne2, Matrix.Storage(storageSize, tnw1xne2)) + let nw1_x_ne2 = + Matrix.SparseMatrix(nrows, ncols, nvals_nw1_x_ne2, Matrix.Storage(storageSize, tnw1_x_ne2)) - let ne1xse2 = - Matrix.SparseMatrix(nrows, ncols, nvals_ne1xse2, Matrix.Storage(storageSize, tne1xse2)) + let ne1_x_se2 = + Matrix.SparseMatrix(nrows, ncols, nvals_ne1_x_se2, Matrix.Storage(storageSize, tne1_x_se2)) - let sw1xnw2 = - Matrix.SparseMatrix(nrows, ncols, nvals_sw1xnw2, Matrix.Storage(storageSize, tsw1xnw2)) + let sw1_x_nw2 = + Matrix.SparseMatrix(nrows, ncols, nvals_sw1_x_nw2, Matrix.Storage(storageSize, tsw1_x_nw2)) - let se1xsw2 = - Matrix.SparseMatrix(nrows, ncols, nvals_se1xsw2, Matrix.Storage(storageSize, tse1xsw2)) + let se1_x_sw2 = + Matrix.SparseMatrix(nrows, ncols, nvals_se1_x_sw2, Matrix.Storage(storageSize, tse1_x_sw2)) - let sw1xne2 = - Matrix.SparseMatrix(nrows, ncols, nvals_sw1xne2, Matrix.Storage(storageSize, tsw1xne2)) + let sw1_x_ne2 = + Matrix.SparseMatrix(nrows, ncols, nvals_sw1_x_ne2, Matrix.Storage(storageSize, tsw1_x_ne2)) - let se1xse2 = - Matrix.SparseMatrix(nrows, ncols, nvals_se1xse2, Matrix.Storage(storageSize, tse1xse2)) + let se1_x_se2 = + Matrix.SparseMatrix(nrows, ncols, nvals_se1_x_se2, Matrix.Storage(storageSize, tse1_x_se2)) let mAdd m1 (m2: Matrix.SparseMatrix<_>) = match m2.storage.data with | Matrix.qtree.Leaf Dummy -> Result.Success m1 | _ -> Matrix.map2 m1 m2 op_add - let rnw = mAdd nw1xnw2 ne1xsw2 - let rne = mAdd nw1xne2 ne1xse2 - let rsw = mAdd sw1xnw2 se1xsw2 - let rse = mAdd sw1xne2 se1xse2 + let rnw = mAdd nw1_x_nw2 ne1_x_sw2 + let rne = mAdd nw1_x_ne2 ne1_x_se2 + let rsw = mAdd sw1_x_nw2 se1_x_sw2 + let rse = mAdd sw1_x_ne2 se1_x_se2 match rnw, rne, rsw, rse with | Result.Success(nw), Result.Success(ne), Result.Success(sw), Result.Success(se) -> From 0ca94b4620176055fe1146cc4465d24d9984c293 Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Thu, 5 Feb 2026 15:05:24 +0300 Subject: [PATCH 09/15] Add triangle counting --- QuadTree.Tests/QuadTree.Tests.fsproj | 1 + QuadTree.Tests/Tests.Matrix.fs | 68 ++++++++++++- QuadTree.Tests/Tests.TriangleCount.fs | 47 +++++++++ QuadTree/LinearAlgebra.fs | 7 +- QuadTree/Matrix.fs | 132 ++++++++++++++++++-------- QuadTree/QuadTree.fsproj | 1 + QuadTree/TriangleCount.fs | 42 ++++++++ 7 files changed, 255 insertions(+), 43 deletions(-) create mode 100644 QuadTree.Tests/Tests.TriangleCount.fs create mode 100644 QuadTree/TriangleCount.fs diff --git a/QuadTree.Tests/QuadTree.Tests.fsproj b/QuadTree.Tests/QuadTree.Tests.fsproj index 8697b9a..dd3c1ad 100644 --- a/QuadTree.Tests/QuadTree.Tests.fsproj +++ b/QuadTree.Tests/QuadTree.Tests.fsproj @@ -11,6 +11,7 @@ + diff --git a/QuadTree.Tests/Tests.Matrix.fs b/QuadTree.Tests/Tests.Matrix.fs index bb382a1..17f9179 100644 --- a/QuadTree.Tests/Tests.Matrix.fs +++ b/QuadTree.Tests/Tests.Matrix.fs @@ -15,7 +15,20 @@ let printMatrix (matrix: SparseMatrix<_>) = printfn " size: %A" matrix.storage.size printfn " Data: %A" matrix.storage.data - +let leaf_v v = qtree.Leaf << UserValue <| Some v +let leaf_n () = qtree.Leaf << UserValue <| None + +let op_add x y = + match (x, y) with + | Some(a), Some(b) -> Some(a + b) + | Some(a), _ + | _, Some(a) -> Some(a) + | _ -> None + +let op_mult x y = + match (x, y) with + | Some(a), Some(b) -> Some(a * b) + | _ -> None (* N,1,1,N 3,2,2,3 @@ -349,3 +362,56 @@ let ``Condensation of sparse`` () = SparseMatrix(4UL, 3UL, 0UL, Storage(4UL, tree)) Assert.Equal(expected.storage.data, actual.storage.data) + +[] +let ``fold -> sum`` () = + // 222D + // 222D + // 222D + // DDDD + let tree = + qtree.Node( + leaf_v 2, + qtree.Node(leaf_v 2, qtree.Leaf Dummy, leaf_v 2, qtree.Leaf Dummy), + qtree.Node(leaf_v 2, leaf_v 2, qtree.Leaf Dummy, qtree.Leaf Dummy), + qtree.Node(leaf_v 2, qtree.Leaf Dummy, qtree.Leaf Dummy, qtree.Leaf Dummy) + ) + + let m1 = + SparseMatrix(3UL, 3UL, 9UL, Matrix.Storage(4UL, tree)) + + let expected = 18 + + let actual = Option.get <| Matrix.fold op_add None m1 + + Assert.Equal(expected, actual) + +[] +let ``4x4 lower triangle`` () = + // 2222 + // 2222 + // 2222 + // 2222 + let tree = leaf_v 2 + + // 2NNN + // 22NN + // 222N + // 2222 + let tree_expected = + qtree.Node( + qtree.Node(leaf_v 2, leaf_n (), leaf_v 2, leaf_v 2), + leaf_n (), + leaf_v 2, + qtree.Node(leaf_v 2, leaf_n (), leaf_v 2, leaf_v 2) + ) + + let m1 = + SparseMatrix(4UL, 4UL, 16UL, Matrix.Storage(4UL, tree)) + + let expected = + SparseMatrix(4UL, 4UL, 10UL, Matrix.Storage(4UL, tree_expected)) + + let actual = getLowerTriangle m1 + + Assert.Equal(expected, actual) diff --git a/QuadTree.Tests/Tests.TriangleCount.fs b/QuadTree.Tests/Tests.TriangleCount.fs new file mode 100644 index 0000000..7a5bb00 --- /dev/null +++ b/QuadTree.Tests/Tests.TriangleCount.fs @@ -0,0 +1,47 @@ +module Graph.TriangleCount.Tests + +open System +open Xunit + +open Common +open Matrix +open Graph.TriangleCount + +[] +let ``7V Triangle count`` () = + // Lower triangle + + // 0 1 2 3 4 5 6 + // 0 0 0 0 0 0 0 0 + // 1 1 0 0 0 0 0 0 + // 2 0 0 0 0 0 0 0 + // 3 1 1 1 0 0 0 0 + // 4 0 1 0 0 0 0 0 + // 5 0 0 1 1 1 0 0 + // 6 0 1 1 1 1 0 0 + + let g = + let d = + [ 1UL, 0UL, () + 3UL, 0UL, () + 3UL, 1UL, () + 3UL, 2UL, () + 4UL, 1UL, () + 5UL, 2UL, () + 5UL, 3UL, () + 5UL, 4UL, () + 6UL, 1UL, () + 6UL, 2UL, () + 6UL, 3UL, () + 6UL, 4UL, () ] + + fromCoordinateList (CoordinateList(7UL, 7UL, d)) + + let expected = 5UL + + let actual = + match triangle_count g with + | Result.Success(Some x) -> x + | _ -> failwith "Unreachable" + + Assert.Equal(expected, actual) diff --git a/QuadTree/LinearAlgebra.fs b/QuadTree/LinearAlgebra.fs index ab4cba6..16a3479 100644 --- a/QuadTree/LinearAlgebra.fs +++ b/QuadTree/LinearAlgebra.fs @@ -107,7 +107,12 @@ type MXMError<'value1, 'value2, 'value3> = | MatrixAdditionProblem of Matrix.Error<'value3, 'value3> -let mxm op_add op_mult (m1: Matrix.SparseMatrix<'a>) (m2: Matrix.SparseMatrix<'b>) = +let mxm + (op_add: 'c option -> 'c option -> 'c option) + (op_mult: 'a option -> 'b option -> 'c option) + (m1: Matrix.SparseMatrix<'a>) + (m2: Matrix.SparseMatrix<'b>) + = let rec shrink tree (size: uint64) = match tree with diff --git a/QuadTree/Matrix.fs b/QuadTree/Matrix.fs index 467ee10..4be0854 100644 --- a/QuadTree/Matrix.fs +++ b/QuadTree/Matrix.fs @@ -180,48 +180,98 @@ let map2 (matrix1: SparseMatrix<_>) (matrix2: SparseMatrix<_>) f = else (Error.InconsistentSizeOfArguments(matrix1, matrix2)) |> Result.Failure +let fold (folder: 'State option -> 'T option -> 'State option) (state: 'State option) (matrix: SparseMatrix<'T>) = + let rec traverse tree (size: uint64) (state: 'State option) = + match tree with + | Leaf Dummy -> state + | Leaf(UserValue v) -> + let mutable accum = state + let area = (uint64 size) * (uint64 size) -(* -let rec map (op: 'TCell1 -> 'TCell2) (m1:Matrix<'TCell1>) = - match m1 with - | Leaf(s,v1) -> - Leaf (s, op v1) - | Node (s,x1,x2,x3,x4) -> - mkNode s (map op x1) (map op x2) (map op x3) (map op x4) + for _ in 1UL .. area do + accum <- folder accum v + accum + | Node(nw, ne, sw, se) -> + let halfSize = size / 2UL -let multScalar opAdd (x:uint) y = - if x = 1u - then y - else - List.init (int x) (fun _ -> y) - |> List.reduce opAdd - -let compose opAdd (opMult: 'TCell1 -> 'TCell2 -> 'TCell3) (m1:Matrix<'TCell1>) (m2:Matrix<'TCell2>) = - - let rec go m1 m2 = - match (m1, m2) with - | Leaf(s,v1), Leaf(_,v2) -> - Leaf (s, multScalar opAdd s (opMult v1 v2)) - | Node (s,x1,x2,x3,x4), Node (_,y1,y2,y3,y4) -> - let z1 = map2 opAdd (go x1 y1) (go x2 y3) - let z2 = map2 opAdd (go x1 y2) (go x2 y4) - let z3 = map2 opAdd (go x3 y1) (go x4 y3) - let z4 = map2 opAdd (go x3 y2) (go x4 y4) - mkNode s z1 z2 z3 z4 - | Node (s,x1,x2,x3,x4), Leaf (_,v) -> - let l = Leaf(s / 2u,v) - let z1 = map2 opAdd (go x1 l) (go x2 l) - let z3 = map2 opAdd (go x3 l) (go x4 l) - mkNode s z1 z1 z3 z3 - | Leaf (_,v), Node (s,x1,x2,x3,x4) -> - let l = Leaf(s / 2u,v) - let z1 = map2 opAdd (go l x1) (go l x3) - let z2 = map2 opAdd (go l x2) (go l x4) - mkNode s z1 z2 z1 z2 - - if m1.Size = m2.Size - then go m1 m2 - else failwith $"Matrices should be of equals size, but m1.Size = {m1.Size} and m2.Size = {m2.Size}" + let nwState = traverse nw halfSize state + let neState = traverse ne halfSize nwState + let swState = traverse sw halfSize neState + let seState = traverse se halfSize swState -*) + seState + + let storageSize = matrix.storage.size + + let tree = matrix.storage.data + traverse tree storageSize state + + +let getLowerTriangle (matrix: SparseMatrix<_>) = + + // returns tree, removed_nvals + let rec makeNone tree (size: uint64) = + match tree with + | Leaf Dummy + | Leaf(UserValue None) -> tree, 0UL + | Leaf(UserValue(Some _)) -> Leaf(UserValue None), (uint64 <| size * size) * 1UL + | Node(nw, ne, sw, se) -> + let halfSize = size / 2UL + let nw_new, nw_removed = makeNone nw halfSize + let ne_new, ne_removed = makeNone ne halfSize + let sw_new, sw_removed = makeNone sw halfSize + let se_new, se_removed = makeNone se halfSize + + (mkNode nw_new ne_new sw_new se_new), nw_removed + ne_removed + sw_removed + se_removed + + let rec traverse tree size = + match tree with + | Leaf _ when size = 1UL -> tree, 0UL + | Leaf Dummy -> Leaf Dummy, 0UL + | Leaf _ -> + let halfSize = size / 2UL + + let nw, nw_removed = traverse tree halfSize + + let ne, ne_removed = + Leaf <| UserValue None, (uint64 <| halfSize * halfSize) * 1UL + + let sw, sw_removed = tree, 0UL + let se, se_removed = traverse tree halfSize + (mkNode nw ne sw se), nw_removed + ne_removed + sw_removed + se_removed + | Node(nw, ne, sw, se) -> + let halfSize = size / 2UL + + let nw_new, nw_removed = traverse nw halfSize + let ne_new, ne_removed = makeNone ne halfSize + let sw_new, sw_removed = sw, 0UL + let se_new, se_removed = traverse se halfSize + + (mkNode nw_new ne_new sw_new se_new), nw_removed + ne_removed + sw_removed + se_removed + + let storageSize = matrix.storage.size + let tree, nvals_removed = traverse matrix.storage.data storageSize + + SparseMatrix(matrix.nrows, matrix.ncols, matrix.nvals - nvals_removed, Storage(storageSize, tree)) + +let transpose (matrix: SparseMatrix<_>) = + let rec traverse tree = + match tree with + | Leaf _ -> tree + | Node(nw, ne, sw, se) -> + mkNode + (traverse nw) + (traverse sw) // ne -> sw + (traverse ne) // sw -> ne + (traverse se) + + let nrows = (uint64 matrix.ncols) * 1UL + let ncols = (uint64 matrix.nrows) * 1UL + + let tree = traverse matrix.storage.data + + SparseMatrix(nrows, ncols, matrix.nvals, Storage(matrix.storage.size, tree)) + +let mask (m1: SparseMatrix<'a>) (m2: SparseMatrix<'b>) f = + map2 m1 m2 (fun m1 m2 -> if f m2 then m1 else None) diff --git a/QuadTree/QuadTree.fsproj b/QuadTree/QuadTree.fsproj index a27fb3d..85fda43 100644 --- a/QuadTree/QuadTree.fsproj +++ b/QuadTree/QuadTree.fsproj @@ -12,6 +12,7 @@ + diff --git a/QuadTree/TriangleCount.fs b/QuadTree/TriangleCount.fs new file mode 100644 index 0000000..53bc623 --- /dev/null +++ b/QuadTree/TriangleCount.fs @@ -0,0 +1,42 @@ +module Graph.TriangleCount + +open Common + +type TriangleCountError<'value1, 'value2, 'value3> = + | MXMError of LinearAlgebra.MXMError<'value1, 'value2, 'value3> + | MaskingError of Matrix.Error<'value3, 'value2> + +// Assume non-oriented graph adjacency matrix +// Some () -> edge, None -> no edge +// Computes triangle count +let triangle_count (graph: Matrix.SparseMatrix) = + let graph = Matrix.getLowerTriangle graph + + let op_add o1 o2 = + match o1, o2 with + | Some x, Some y -> Some <| x + y + | Some x, None + | None, Some x -> Some x + | None, None -> None + + let op_mult o1 o2 = + match o1, o2 with + | Some(), Some() -> Some 1UL + | _ -> None + + let C = LinearAlgebra.mxm op_add op_mult graph (Matrix.transpose graph) + + let CMasked = + match C with + | Result.Success matrix -> + match Matrix.mask matrix graph Option.isSome with + | Result.Success m -> Result.Success m + | Result.Failure e -> Result.Failure <| TriangleCountError.MaskingError e + | Result.Failure e -> Result.Failure <| TriangleCountError.MXMError e + + let result = + match CMasked with + | Result.Success matrix -> Result.Success(Matrix.fold op_add None matrix) + | Result.Failure e -> Result.Failure e + + result From 167cb293784046b6a6ea74f8590aaccb43125c59 Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Thu, 5 Feb 2026 16:03:29 +0300 Subject: [PATCH 10/15] Make triangle_count generic --- QuadTree/TriangleCount.fs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/QuadTree/TriangleCount.fs b/QuadTree/TriangleCount.fs index 53bc623..c291fac 100644 --- a/QuadTree/TriangleCount.fs +++ b/QuadTree/TriangleCount.fs @@ -7,9 +7,9 @@ type TriangleCountError<'value1, 'value2, 'value3> = | MaskingError of Matrix.Error<'value3, 'value2> // Assume non-oriented graph adjacency matrix -// Some () -> edge, None -> no edge +// Some _ -> edge, None -> no edge // Computes triangle count -let triangle_count (graph: Matrix.SparseMatrix) = +let triangle_count (graph: Matrix.SparseMatrix<_>) = let graph = Matrix.getLowerTriangle graph let op_add o1 o2 = @@ -21,7 +21,7 @@ let triangle_count (graph: Matrix.SparseMatrix) = let op_mult o1 o2 = match o1, o2 with - | Some(), Some() -> Some 1UL + | Some _, Some _ -> Some 1UL | _ -> None let C = LinearAlgebra.mxm op_add op_mult graph (Matrix.transpose graph) @@ -40,3 +40,5 @@ let triangle_count (graph: Matrix.SparseMatrix) = | Result.Failure e -> Result.Failure e result + + From 419195b36d37a5a6494b34bb42ab42f5a4eef92b Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Thu, 5 Feb 2026 16:49:24 +0300 Subject: [PATCH 11/15] Make more use of helpers --- QuadTree.Tests/Tests.LinearAlgebra.fs | 96 ++++------- QuadTree.Tests/Tests.Matrix.fs | 237 ++++++++++++-------------- QuadTree/TriangleCount.fs | 2 - 3 files changed, 143 insertions(+), 192 deletions(-) diff --git a/QuadTree.Tests/Tests.LinearAlgebra.fs b/QuadTree.Tests/Tests.LinearAlgebra.fs index c03bb8d..8e3dd45 100644 --- a/QuadTree.Tests/Tests.LinearAlgebra.fs +++ b/QuadTree.Tests/Tests.LinearAlgebra.fs @@ -32,6 +32,13 @@ let op_mult x y = let leaf_v v = qtree.Leaf << UserValue <| Some v let leaf_n () = qtree.Leaf << UserValue <| None +let leaf_d () = qtree.Leaf Dummy + +let vleaf_v v = + Vector.btree.Leaf << UserValue <| Some v + +let vleaf_n () = Vector.btree.Leaf << UserValue <| None +let vleaf_d () = Vector.btree.Leaf Dummy [] let ``Simple vxm. All sizes are power of two.`` () = @@ -40,7 +47,7 @@ let ``Simple vxm. All sizes are power of two.`` () = Matrix.qtree.Node( Matrix.qtree.Node(leaf_n (), leaf_v 1, leaf_v 3, leaf_v 2), Matrix.qtree.Node(leaf_v 1, leaf_n (), leaf_v 2, leaf_v 3), - Matrix.qtree.Leaf(UserValue(None)), + leaf_n (), Matrix.qtree.Node(leaf_v 1, leaf_v 2, leaf_v 3, leaf_n ()) ) @@ -48,17 +55,13 @@ let ``Simple vxm. All sizes are power of two.`` () = SparseMatrix(4UL, 4UL, 9UL, store) let v = - let tree = Vector.btree.Leaf(UserValue(Some(2))) + let tree = vleaf_v 2 let store = Vector.Storage(4UL, tree) SparseVector(4UL, 4UL, store) let expected = - let tree = - Vector.btree.Node( - Vector.btree.Leaf(UserValue(Some(6))), - Vector.btree.Node(Vector.btree.Leaf(UserValue(Some(14))), Vector.btree.Leaf(UserValue(Some(10)))) - ) + let tree = Vector.btree.Node(vleaf_v 6, Vector.btree.Node(vleaf_v 14, vleaf_v 10)) let store = Vector.Storage(4UL, tree) Result.Success(SparseVector(4UL, 4UL, store)) @@ -84,29 +87,21 @@ let ``Simple vxm. 3 * (3x4)`` () = Matrix.qtree.Node( Matrix.qtree.Node(leaf_n (), leaf_v 1, leaf_v 3, leaf_v 2), Matrix.qtree.Node(leaf_v 1, leaf_n (), leaf_v 2, leaf_v 3), - Matrix.qtree.Node(leaf_n (), leaf_n (), Matrix.qtree.Leaf(Dummy), Matrix.qtree.Leaf(Dummy)), - Matrix.qtree.Node(leaf_v 1, leaf_v 2, Matrix.qtree.Leaf(Dummy), Matrix.qtree.Leaf(Dummy)) + Matrix.qtree.Node(leaf_n (), leaf_n (), leaf_d (), leaf_d ()), + Matrix.qtree.Node(leaf_v 1, leaf_v 2, leaf_d (), leaf_d ()) ) let store = Matrix.Storage(4UL, tree) SparseMatrix(3UL, 4UL, 8UL, store) let v = - let tree = - Vector.btree.Node( - Vector.btree.Leaf(UserValue(Some(2))), - Vector.btree.Node(Vector.btree.Leaf(UserValue(Some(2))), Vector.btree.Leaf(Dummy)) - ) + let tree = Vector.btree.Node(vleaf_v 2, Vector.btree.Node(vleaf_v 2, vleaf_d ())) let store = Vector.Storage(4UL, tree) SparseVector(3UL, 3UL, store) let expected = - let tree = - Vector.btree.Node( - Vector.btree.Leaf(UserValue(Some(6))), - Vector.btree.Node(Vector.btree.Leaf(UserValue(Some(8))), Vector.btree.Leaf(UserValue(Some(10)))) - ) + let tree = Vector.btree.Node(vleaf_v 6, Vector.btree.Node(vleaf_v 8, vleaf_v 10)) let store = Vector.Storage(4UL, tree) Result.Success(SparseVector(4UL, 4UL, store)) @@ -132,27 +127,23 @@ let ``Simple vxm. 4 * (4x3).`` () = let tree = Matrix.qtree.Node( Matrix.qtree.Node(leaf_n (), leaf_v 1, leaf_v 3, leaf_v 2), - Matrix.qtree.Node(leaf_v 1, Matrix.qtree.Leaf(Dummy), leaf_v 2, Matrix.qtree.Leaf(Dummy)), - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Node(leaf_v 1, Matrix.qtree.Leaf(Dummy), leaf_v 3, Matrix.qtree.Leaf(Dummy)) + Matrix.qtree.Node(leaf_v 1, leaf_d (), leaf_v 2, leaf_d ()), + leaf_n (), + Matrix.qtree.Node(leaf_v 1, leaf_d (), leaf_v 3, leaf_d ()) ) let store = Matrix.Storage(4UL, tree) SparseMatrix(4UL, 3UL, 7UL, store) let v = - let tree = Vector.btree.Leaf(UserValue(Some(2))) + let tree = vleaf_v 2 let store = Vector.Storage(4UL, tree) SparseVector(4UL, 4UL, store) let expected = - let tree = - Vector.btree.Node( - Vector.btree.Leaf(UserValue(Some(6))), - Vector.btree.Node(Vector.btree.Leaf(UserValue(Some(14))), Vector.btree.Leaf(Dummy)) - ) + let tree = Vector.btree.Node(vleaf_v 6, Vector.btree.Node(vleaf_v 14, vleaf_d ())) let store = Vector.Storage(4UL, tree) Result.Success(SparseVector(3UL, 3UL, store)) @@ -184,33 +175,24 @@ let ``Simple vxm. 3 * (3x5)`` () = Matrix.qtree.Node( Matrix.qtree.Node(leaf_n (), leaf_v 1, leaf_v 3, leaf_v 2), Matrix.qtree.Node(leaf_v 1, leaf_n (), leaf_v 2, leaf_v 3), - Matrix.qtree.Node(leaf_n (), leaf_n (), Matrix.qtree.Leaf(Dummy), Matrix.qtree.Leaf(Dummy)), - Matrix.qtree.Node(leaf_v 1, leaf_v 2, Matrix.qtree.Leaf(Dummy), Matrix.qtree.Leaf(Dummy)) + Matrix.qtree.Node(leaf_n (), leaf_n (), leaf_d (), leaf_d ()), + Matrix.qtree.Node(leaf_v 1, leaf_v 2, leaf_d (), leaf_d ()) ), Matrix.qtree.Node( - Matrix.qtree.Node(leaf_n (), Matrix.qtree.Leaf(Dummy), leaf_v 1, Matrix.qtree.Leaf(Dummy)), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Node( - leaf_n (), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy) - ), - Matrix.qtree.Leaf(Dummy) + Matrix.qtree.Node(leaf_n (), leaf_d (), leaf_v 1, leaf_d ()), + leaf_d (), + Matrix.qtree.Node(leaf_n (), leaf_d (), leaf_d (), leaf_d ()), + leaf_d () ), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy) + leaf_d (), + leaf_d () ) let store = Matrix.Storage(8UL, tree) SparseMatrix(3UL, 5UL, 9UL, store) let v = - let tree = - Vector.btree.Node( - Vector.btree.Leaf(UserValue(Some(2))), - Vector.btree.Node(Vector.btree.Leaf(UserValue(Some(2))), Vector.btree.Leaf(Dummy)) - ) + let tree = Vector.btree.Node(vleaf_v 2, Vector.btree.Node(vleaf_v 2, vleaf_d ())) let store = Vector.Storage(4UL, tree) SparseVector(3UL, 3UL, store) @@ -218,14 +200,8 @@ let ``Simple vxm. 3 * (3x5)`` () = let expected = let tree = Vector.btree.Node( - Vector.btree.Node( - Vector.btree.Leaf(UserValue(Some(6))), - Vector.btree.Node(Vector.btree.Leaf(UserValue(Some(8))), Vector.btree.Leaf(UserValue(Some(10)))) - ), - Vector.btree.Node( - Vector.btree.Node(Vector.btree.Leaf(UserValue(Some(2))), Vector.btree.Leaf(Dummy)), - Vector.btree.Leaf(Dummy) - ) + Vector.btree.Node(vleaf_v 6, Vector.btree.Node(vleaf_v 8, vleaf_v 10)), + Vector.btree.Node(Vector.btree.Node(vleaf_v 2, vleaf_d ()), vleaf_d ()) ) let store = Vector.Storage(8UL, tree) @@ -244,17 +220,17 @@ let ``Simple mxm`` () = let tree = qtree.Node( leaf_v 2, - qtree.Node(leaf_v 2, qtree.Leaf Dummy, leaf_v 2, qtree.Leaf Dummy), - qtree.Node(leaf_v 2, leaf_v 2, qtree.Leaf Dummy, qtree.Leaf Dummy), - qtree.Node(leaf_v 2, qtree.Leaf Dummy, qtree.Leaf Dummy, qtree.Leaf Dummy) + qtree.Node(leaf_v 2, leaf_d (), leaf_v 2, leaf_d ()), + qtree.Node(leaf_v 2, leaf_v 2, leaf_d (), leaf_d ()), + qtree.Node(leaf_v 2, leaf_d (), leaf_d (), leaf_d ()) ) let tree_expected = qtree.Node( leaf_v 12, - qtree.Node(leaf_v 12, qtree.Leaf Dummy, leaf_v 12, qtree.Leaf Dummy), - qtree.Node(leaf_v 12, leaf_v 12, qtree.Leaf Dummy, qtree.Leaf Dummy), - qtree.Node(leaf_v 12, qtree.Leaf Dummy, qtree.Leaf Dummy, qtree.Leaf Dummy) + qtree.Node(leaf_v 12, leaf_d (), leaf_v 12, leaf_d ()), + qtree.Node(leaf_v 12, leaf_v 12, leaf_d (), leaf_d ()), + qtree.Node(leaf_v 12, leaf_d (), leaf_d (), leaf_d ()) ) let m1 = diff --git a/QuadTree.Tests/Tests.Matrix.fs b/QuadTree.Tests/Tests.Matrix.fs index 17f9179..d97f7ef 100644 --- a/QuadTree.Tests/Tests.Matrix.fs +++ b/QuadTree.Tests/Tests.Matrix.fs @@ -17,6 +17,7 @@ let printMatrix (matrix: SparseMatrix<_>) = let leaf_v v = qtree.Leaf << UserValue <| Some v let leaf_n () = qtree.Leaf << UserValue <| None +let leaf_d () = qtree.Leaf Dummy let op_add x y = match (x, y) with @@ -50,38 +51,17 @@ let ``Simple Matrix.map2. Square where number of cols and rows are power of two. let m1 = let tree = Matrix.qtree.Node( - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(UserValue(Some(2))) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(UserValue(Some(3))) - ), - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(UserValue(None)) - ) + Matrix.qtree.Node(leaf_n (), leaf_v 1, leaf_v 3, leaf_v 2), + Matrix.qtree.Node(leaf_v 1, leaf_n (), leaf_v 2, leaf_v 3), + leaf_n (), + Matrix.qtree.Node(leaf_v 1, leaf_v 2, leaf_v 3, leaf_n ()) ) let store = Storage(4UL, tree) SparseMatrix(4UL, 4UL, 9UL, store) let m2 = - let tree = - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(UserValue(None)) - ) + let tree = Matrix.qtree.Node(leaf_v 1, leaf_v 2, leaf_v 3, leaf_n ()) let store = Storage(4UL, tree) SparseMatrix(4UL, 4UL, 12UL, store) @@ -94,20 +74,10 @@ let ``Simple Matrix.map2. Square where number of cols and rows are power of two. let expected = let tree = Matrix.qtree.Node( - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(UserValue(Some(4))), - Matrix.qtree.Leaf(UserValue(Some(3))) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(Some(4))), - Matrix.qtree.Leaf(UserValue(Some(5))) - ), - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(None)) + Matrix.qtree.Node(leaf_n (), leaf_v 2, leaf_v 4, leaf_v 3), + Matrix.qtree.Node(leaf_v 3, leaf_n (), leaf_v 4, leaf_v 5), + leaf_n (), + leaf_n () ) let store = Storage(4UL, tree) @@ -138,30 +108,10 @@ let ``Simple Matrix.map2. Square where number of cols and rows are not power of let m1 = let tree = Matrix.qtree.Node( - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(UserValue(Some(2))) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(Dummy) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy) - ) + Matrix.qtree.Node(leaf_n (), leaf_v 1, leaf_v 3, leaf_v 2), + Matrix.qtree.Node(leaf_v 1, leaf_d (), leaf_v 2, leaf_d ()), + Matrix.qtree.Node(leaf_n (), leaf_n (), leaf_d (), leaf_d ()), + Matrix.qtree.Node(leaf_v 1, leaf_d (), leaf_d (), leaf_d ()) ) let store = Storage(4UL, tree) @@ -170,25 +120,10 @@ let ``Simple Matrix.map2. Square where number of cols and rows are not power of let m2 = let tree = Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(1))), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(Dummy) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy) - ) + leaf_v 1, + Matrix.qtree.Node(leaf_v 2, leaf_d (), leaf_v 2, leaf_d ()), + Matrix.qtree.Node(leaf_v 3, leaf_v 3, leaf_d (), leaf_d ()), + Matrix.qtree.Node(leaf_n (), leaf_d (), leaf_d (), leaf_d ()) ) let store = Storage(4UL, tree) @@ -202,30 +137,10 @@ let ``Simple Matrix.map2. Square where number of cols and rows are not power of let expected = let tree = Matrix.qtree.Node( - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(Some(2))), - Matrix.qtree.Leaf(UserValue(Some(4))), - Matrix.qtree.Leaf(UserValue(Some(3))) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(Some(3))), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(UserValue(Some(4))), - Matrix.qtree.Leaf(Dummy) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy) - ), - Matrix.qtree.Node( - Matrix.qtree.Leaf(UserValue(None)), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy), - Matrix.qtree.Leaf(Dummy) - ) + Matrix.qtree.Node(leaf_n (), leaf_v 2, leaf_v 4, leaf_v 3), + Matrix.qtree.Node(leaf_v 3, leaf_d (), leaf_v 4, leaf_d ()), + Matrix.qtree.Node(leaf_n (), leaf_n (), leaf_d (), leaf_d ()), + Matrix.qtree.Node(leaf_n (), leaf_d (), leaf_d (), leaf_d ()) ) let store = Storage(4UL, tree) @@ -316,12 +231,7 @@ let ``Condensation of empty`` () = // DDDD // DDDD let tree = - qtree.Node( - qtree.Leaf <| UserValue None, - qtree.Node(qtree.Leaf <| UserValue None, qtree.Leaf Dummy, qtree.Leaf <| UserValue None, qtree.Leaf Dummy), - qtree.Leaf Dummy, - qtree.Leaf Dummy - ) + qtree.Node(leaf_n (), qtree.Node(leaf_n (), leaf_d (), leaf_n (), leaf_d ()), leaf_d (), leaf_d ()) let expected = SparseMatrix(2UL, 3UL, 0UL, Storage(4UL, tree)) @@ -342,20 +252,10 @@ let ``Condensation of sparse`` () = let tree = qtree.Node( - qtree.Leaf <| UserValue None, - qtree.Node( - qtree.Leaf << UserValue <| Some 2, - qtree.Leaf Dummy, - qtree.Leaf <| UserValue None, - qtree.Leaf Dummy - ), - qtree.Leaf <| UserValue None, - qtree.Node( - qtree.Leaf <| UserValue None, - qtree.Leaf Dummy, - qtree.Leaf << UserValue <| Some 4, - qtree.Leaf Dummy - ) + leaf_n (), + qtree.Node(leaf_v 2, leaf_d (), leaf_n (), leaf_d ()), + leaf_n (), + qtree.Node(leaf_n (), leaf_d (), leaf_v 4, leaf_d ()) ) let expected = @@ -372,9 +272,9 @@ let ``fold -> sum`` () = let tree = qtree.Node( leaf_v 2, - qtree.Node(leaf_v 2, qtree.Leaf Dummy, leaf_v 2, qtree.Leaf Dummy), - qtree.Node(leaf_v 2, leaf_v 2, qtree.Leaf Dummy, qtree.Leaf Dummy), - qtree.Node(leaf_v 2, qtree.Leaf Dummy, qtree.Leaf Dummy, qtree.Leaf Dummy) + qtree.Node(leaf_v 2, leaf_d (), leaf_v 2, leaf_d ()), + qtree.Node(leaf_v 2, leaf_v 2, leaf_d (), leaf_d ()), + qtree.Node(leaf_v 2, leaf_d (), leaf_d (), leaf_d ()) ) let m1 = @@ -415,3 +315,80 @@ let ``4x4 lower triangle`` () = let actual = getLowerTriangle m1 Assert.Equal(expected, actual) + + +[] +let ``3x3 lower triangle`` () = + // 222 D + // N22 D + // NN2 D + + // DDD D + let tree = + qtree.Node( + qtree.Node(leaf_v 2, leaf_v 2, leaf_n (), leaf_v 2), + qtree.Node(leaf_v 2, leaf_d (), leaf_v 2, leaf_d ()), + qtree.Node(leaf_n (), leaf_n (), leaf_d (), leaf_d ()), + qtree.Node(leaf_v 2, leaf_d (), leaf_d (), leaf_d ()) + ) + + + // 2NN D + // N2N D + // NN2 D + + // DDD D + let tree_expected = + qtree.Node( + qtree.Node(leaf_v 2, leaf_n (), leaf_n (), leaf_v 2), + qtree.Node(leaf_n (), leaf_d (), leaf_n (), leaf_d ()), + qtree.Node(leaf_n (), leaf_n (), leaf_d (), leaf_d ()), + qtree.Node(leaf_v 2, leaf_d (), leaf_d (), leaf_d ()) + ) + + let m1 = + SparseMatrix(3UL, 3UL, 6UL, Matrix.Storage(4UL, tree)) + + let expected = + SparseMatrix(3UL, 3UL, 3UL, Matrix.Storage(4UL, tree_expected)) + + let actual = getLowerTriangle m1 + + Assert.Equal(expected, actual) + +[] +let ``2x3 transposition`` () = + // 2N2D + // N2ND + // DDDD + // DDDD + let tree = + qtree.Node( + qtree.Node(leaf_v 2, leaf_n (), leaf_n (), leaf_v 2), + qtree.Node(leaf_v 2, leaf_d (), leaf_n (), leaf_d ()), + leaf_d (), + leaf_d () + ) + + + // 2NDD + // N2DD + // 2NDD + // DDDD + let tree_expected = + qtree.Node( + qtree.Node(leaf_v 2, leaf_n (), leaf_n (), leaf_v 2), + leaf_d (), + qtree.Node(leaf_v 2, leaf_n (), leaf_d (), leaf_d ()), + leaf_d () + ) + + let m1 = + SparseMatrix(2UL, 3UL, 3UL, Matrix.Storage(4UL, tree)) + + let expected = + SparseMatrix(3UL, 2UL, 3UL, Matrix.Storage(4UL, tree_expected)) + + let actual = transpose m1 + + Assert.Equal(expected, actual) diff --git a/QuadTree/TriangleCount.fs b/QuadTree/TriangleCount.fs index c291fac..763bc16 100644 --- a/QuadTree/TriangleCount.fs +++ b/QuadTree/TriangleCount.fs @@ -40,5 +40,3 @@ let triangle_count (graph: Matrix.SparseMatrix<_>) = | Result.Failure e -> Result.Failure e result - - From 544900e72b478eb43a1eaf1888f4305101ec36cc Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Thu, 5 Feb 2026 16:56:50 +0300 Subject: [PATCH 12/15] Add upper triangle part in count test --- QuadTree.Tests/Tests.TriangleCount.fs | 32 +++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/QuadTree.Tests/Tests.TriangleCount.fs b/QuadTree.Tests/Tests.TriangleCount.fs index 7a5bb00..d7f1652 100644 --- a/QuadTree.Tests/Tests.TriangleCount.fs +++ b/QuadTree.Tests/Tests.TriangleCount.fs @@ -11,14 +11,15 @@ open Graph.TriangleCount let ``7V Triangle count`` () = // Lower triangle - // 0 1 2 3 4 5 6 - // 0 0 0 0 0 0 0 0 - // 1 1 0 0 0 0 0 0 - // 2 0 0 0 0 0 0 0 - // 3 1 1 1 0 0 0 0 - // 4 0 1 0 0 0 0 0 - // 5 0 0 1 1 1 0 0 - // 6 0 1 1 1 1 0 0 + // 0 1 2 3 4 5 6 + // + // 0 0 1 0 1 0 0 0 + // 1 1 0 0 1 1 0 1 + // 2 0 0 0 1 0 1 1 + // 3 1 1 1 0 0 1 1 + // 4 0 1 0 0 0 1 1 + // 5 0 0 1 1 1 0 0 + // 6 0 1 1 1 1 0 0 let g = let d = @@ -33,7 +34,20 @@ let ``7V Triangle count`` () = 6UL, 1UL, () 6UL, 2UL, () 6UL, 3UL, () - 6UL, 4UL, () ] + 6UL, 4UL, () + + 0UL, 1UL, () + 0UL, 3UL, () + 1UL, 3UL, () + 2UL, 3UL, () + 1UL, 4UL, () + 2UL, 5UL, () + 3UL, 5UL, () + 4UL, 5UL, () + 1UL, 6UL, () + 2UL, 6UL, () + 3UL, 6UL, () + 4UL, 6UL, () ] fromCoordinateList (CoordinateList(7UL, 7UL, d)) From 7f9c74d92e4b1e2361bf502fca4051fea7cd7d20 Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Thu, 5 Feb 2026 17:02:28 +0300 Subject: [PATCH 13/15] Remove unhelpful comment --- QuadTree.Tests/Tests.TriangleCount.fs | 1 - 1 file changed, 1 deletion(-) diff --git a/QuadTree.Tests/Tests.TriangleCount.fs b/QuadTree.Tests/Tests.TriangleCount.fs index d7f1652..357be7c 100644 --- a/QuadTree.Tests/Tests.TriangleCount.fs +++ b/QuadTree.Tests/Tests.TriangleCount.fs @@ -9,7 +9,6 @@ open Graph.TriangleCount [] let ``7V Triangle count`` () = - // Lower triangle // 0 1 2 3 4 5 6 // From 9d5c47845c4e96fbd94929fe5326484f9ab27b9b Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Thu, 5 Feb 2026 17:51:42 +0300 Subject: [PATCH 14/15] Make fold use recursion --- QuadTree.Tests/Tests.Matrix.fs | 23 +++++++++++++++++++++++ QuadTree/Matrix.fs | 10 ++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/QuadTree.Tests/Tests.Matrix.fs b/QuadTree.Tests/Tests.Matrix.fs index d97f7ef..26ac729 100644 --- a/QuadTree.Tests/Tests.Matrix.fs +++ b/QuadTree.Tests/Tests.Matrix.fs @@ -392,3 +392,26 @@ let ``2x3 transposition`` () = let actual = transpose m1 Assert.Equal(expected, actual) + +[] +let ``Fold sum`` () = + // 2N2D + // N2ND + // DDDD + // DDDD + let tree = + qtree.Node( + qtree.Node(leaf_v 2, leaf_n (), leaf_n (), leaf_v 2), + qtree.Node(leaf_v 2, leaf_d (), leaf_n (), leaf_d ()), + leaf_d (), + leaf_d () + ) + + let m1 = + SparseMatrix(2UL, 3UL, 3UL, Matrix.Storage(4UL, tree)) + + let expected = 6 + + let actual = fold op_add None m1 |> Option.get + + Assert.Equal(expected, actual) diff --git a/QuadTree/Matrix.fs b/QuadTree/Matrix.fs index 4be0854..f6e151e 100644 --- a/QuadTree/Matrix.fs +++ b/QuadTree/Matrix.fs @@ -185,13 +185,15 @@ let fold (folder: 'State option -> 'T option -> 'State option) (state: 'State op match tree with | Leaf Dummy -> state | Leaf(UserValue v) -> - let mutable accum = state let area = (uint64 size) * (uint64 size) - for _ in 1UL .. area do - accum <- folder accum v + let rec foldArea count accum = + if count = 0UL then + accum + else + foldArea (count - 1UL) (folder accum v) - accum + foldArea area state | Node(nw, ne, sw, se) -> let halfSize = size / 2UL From 8b8d36ab004d3cf8a9f8f285ab41aed3603974f4 Mon Sep 17 00:00:00 2001 From: Danil-Zaripov Date: Thu, 5 Feb 2026 18:04:11 +0300 Subject: [PATCH 15/15] Assume folder is associative --- QuadTree.Tests/Tests.Matrix.fs | 4 ++-- QuadTree/Matrix.fs | 14 ++++++++------ QuadTree/TriangleCount.fs | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/QuadTree.Tests/Tests.Matrix.fs b/QuadTree.Tests/Tests.Matrix.fs index 26ac729..a7f4553 100644 --- a/QuadTree.Tests/Tests.Matrix.fs +++ b/QuadTree.Tests/Tests.Matrix.fs @@ -282,7 +282,7 @@ let ``fold -> sum`` () = let expected = 18 - let actual = Option.get <| Matrix.fold op_add None m1 + let actual = Option.get <| Matrix.foldAssociative op_add None m1 Assert.Equal(expected, actual) @@ -412,6 +412,6 @@ let ``Fold sum`` () = let expected = 6 - let actual = fold op_add None m1 |> Option.get + let actual = foldAssociative op_add None m1 |> Option.get Assert.Equal(expected, actual) diff --git a/QuadTree/Matrix.fs b/QuadTree/Matrix.fs index f6e151e..7697762 100644 --- a/QuadTree/Matrix.fs +++ b/QuadTree/Matrix.fs @@ -180,20 +180,22 @@ let map2 (matrix1: SparseMatrix<_>) (matrix2: SparseMatrix<_>) f = else (Error.InconsistentSizeOfArguments(matrix1, matrix2)) |> Result.Failure -let fold (folder: 'State option -> 'T option -> 'State option) (state: 'State option) (matrix: SparseMatrix<'T>) = - let rec traverse tree (size: uint64) (state: 'State option) = +let foldAssociative (folder: 'T option -> 'T option -> 'T option) (state: 'T option) (matrix: SparseMatrix<'T>) = + let rec traverse tree (size: uint64) (state: 'T option) = match tree with | Leaf Dummy -> state | Leaf(UserValue v) -> let area = (uint64 size) * (uint64 size) - let rec foldArea count accum = - if count = 0UL then + let rec foldValue size accum = + if size = 1UL then accum else - foldArea (count - 1UL) (folder accum v) + let halfSize = size / 2UL - foldArea area state + foldValue halfSize (folder accum accum) + + folder state (foldValue area v) | Node(nw, ne, sw, se) -> let halfSize = size / 2UL diff --git a/QuadTree/TriangleCount.fs b/QuadTree/TriangleCount.fs index 763bc16..f1d41b6 100644 --- a/QuadTree/TriangleCount.fs +++ b/QuadTree/TriangleCount.fs @@ -36,7 +36,7 @@ let triangle_count (graph: Matrix.SparseMatrix<_>) = let result = match CMasked with - | Result.Success matrix -> Result.Success(Matrix.fold op_add None matrix) + | Result.Success matrix -> Result.Success(Matrix.foldAssociative op_add None matrix) | Result.Failure e -> Result.Failure e result