From 030535348da0774ee8139560a417916d9f99afda Mon Sep 17 00:00:00 2001 From: Narysev Date: Fri, 12 Jun 2026 22:04:49 +0300 Subject: [PATCH 1/5] feat(quadtree): add filter, exists, forall with tests and benchmarks Implement: - Vector.filter / Matrix.filter - Vector.exists / Vector.forall (short-circuit || / &&) - Matrix.exists / Matrix.forall (short-circuit || / &&) Tests (64 total): - Vector.filter: 9 - Vector.exists: 12 - Vector.forall: 12 - Matrix.filter: 9 - Matrix.exists: 11 - Matrix.forall: 11 Benchmarks: - Filters.fs: Vector/Matrix filter (dense + sparse), N=64,256,4096 - Exists.fs: Vector/Matrix exists (dense + sparse), N=64,256,4096 - Forall.fs: Vector/Matrix forall (dense + sparse), N=64,256,4096 - Register in BenchmarkSwitcher and .fsproj --- QuadTree.Benchmark/Exists.fs | 52 +++ QuadTree.Benchmark/Filters.fs | 52 +++ QuadTree.Benchmark/Forall.fs | 52 +++ QuadTree.Benchmark/Main.fs | 5 +- QuadTree.Benchmark/QuadTree.Benchmark.fsproj | 3 + QuadTree.Tests/Tests.Matrix.fs | 354 +++++++++++++++++ QuadTree.Tests/Tests.Vector.fs | 383 +++++++++++++++++++ QuadTree/Matrix.fs | 50 +++ QuadTree/Vector.fs | 40 +- 9 files changed, 989 insertions(+), 2 deletions(-) create mode 100644 QuadTree.Benchmark/Exists.fs create mode 100644 QuadTree.Benchmark/Filters.fs create mode 100644 QuadTree.Benchmark/Forall.fs diff --git a/QuadTree.Benchmark/Exists.fs b/QuadTree.Benchmark/Exists.fs new file mode 100644 index 0000000..2fc08a8 --- /dev/null +++ b/QuadTree.Benchmark/Exists.fs @@ -0,0 +1,52 @@ +namespace QuadTree.Benchmarks.Exists + + +open BenchmarkDotNet.Attributes +open QuadTree.Benchmarks.Utils + +[)>] +type Benchmark() = + + let mutable denseVec = Unchecked.defaultof> + let mutable sparseVec = Unchecked.defaultof> + let mutable denseMat = Unchecked.defaultof> + let mutable sparseMat = Unchecked.defaultof> + + [] + member val N = 0 with get, set + + [] + member this.Setup() = + let denseData = [ for i in 0UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + denseVec <- Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, denseData)) + + let sparseData = [ for i in 0UL .. 10UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + sparseVec <- Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, sparseData)) + + let denseMatData = [ + for r in 0UL .. uint64 this.N - 1UL do + for c in 0UL .. uint64 this.N - 1UL do + (r * 1UL, c * 1UL, int (r + c) % 100) ] + denseMat <- Matrix.fromCoordinateList (Matrix.CoordinateList(uint64 this.N * 1UL, uint64 this.N * 1UL, denseMatData)) + + let sparseMatData = [ + for r in 0UL .. 10UL .. uint64 this.N - 1UL do + for c in 0UL .. 10UL .. uint64 this.N - 1UL do + (r * 1UL, c * 1UL, int (r + c) % 100) ] + sparseMat <- Matrix.fromCoordinateList (Matrix.CoordinateList(uint64 this.N * 1UL, uint64 this.N * 1UL, sparseMatData)) + + [] + member this.VectorExistsDense() = + Vector.exists denseVec (fun x -> x < 0) + + [] + member this.VectorExistsSparse() = + Vector.exists sparseVec (fun x -> x < 0) + + [] + member this.MatrixExistsDense() = + Matrix.exists denseMat (fun x -> x < 0) + + [] + member this.MatrixExistsSparse() = + Matrix.exists sparseMat (fun x -> x < 0) diff --git a/QuadTree.Benchmark/Filters.fs b/QuadTree.Benchmark/Filters.fs new file mode 100644 index 0000000..075e339 --- /dev/null +++ b/QuadTree.Benchmark/Filters.fs @@ -0,0 +1,52 @@ +namespace QuadTree.Benchmarks.Filters + +open BenchmarkDotNet.Attributes +open QuadTree.Benchmarks.Utils + +[)>] +type Benchmark() = + + let mutable denseVec = Unchecked.defaultof> + let mutable sparseVec = Unchecked.defaultof> + let mutable denseMat = Unchecked.defaultof> + let mutable sparseMat = Unchecked.defaultof> + + // [] + [] + member val N = 0 with get, set + + [] + member this.Setup() = + let denseData = [ for i in 0UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + denseVec <- Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, denseData)) + + let sparseData = [ for i in 0UL .. 10UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + sparseVec <- Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, sparseData)) + + let denseMatData = [ + for r in 0UL .. uint64 this.N - 1UL do + for c in 0UL .. uint64 this.N - 1UL do + (r * 1UL, c * 1UL, int (r + c) % 100) ] + denseMat <- Matrix.fromCoordinateList (Matrix.CoordinateList(uint64 this.N * 1UL, uint64 this.N * 1UL, denseMatData)) + + let sparseMatData = [ + for r in 0UL .. 10UL .. uint64 this.N - 1UL do + for c in 0UL .. 10UL .. uint64 this.N - 1UL do + (r * 1UL, c * 1UL, int (r + c) % 100) ] + sparseMat <- Matrix.fromCoordinateList (Matrix.CoordinateList(uint64 this.N * 1UL, uint64 this.N * 1UL, sparseMatData)) + + [] + member this.VectorFilterDense() = + Vector.filter denseVec (fun x -> x % 2 = 0) + + [] + member this.VectorFilterSparse() = + Vector.filter sparseVec (fun x -> x % 2 = 0) + + [] + member this.MatrixFilterDense() = + Matrix.filter denseMat (fun x -> x % 2 = 0) + + [] + member this.MatrixFilterSparse() = + Matrix.filter sparseMat (fun x -> x % 2 = 0) diff --git a/QuadTree.Benchmark/Forall.fs b/QuadTree.Benchmark/Forall.fs new file mode 100644 index 0000000..8d8ad46 --- /dev/null +++ b/QuadTree.Benchmark/Forall.fs @@ -0,0 +1,52 @@ +namespace QuadTree.Benchmarks.Forall + + +open BenchmarkDotNet.Attributes +open QuadTree.Benchmarks.Utils + +[)>] +type Benchmark() = + + let mutable denseVec = Unchecked.defaultof> + let mutable sparseVec = Unchecked.defaultof> + let mutable denseMat = Unchecked.defaultof> + let mutable sparseMat = Unchecked.defaultof> + + [] + member val N = 0 with get, set + + [] + member this.Setup() = + let denseData = [ for i in 0UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + denseVec <- Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, denseData)) + + let sparseData = [ for i in 0UL .. 10UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + sparseVec <- Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, sparseData)) + + let denseMatData = [ + for r in 0UL .. uint64 this.N - 1UL do + for c in 0UL .. uint64 this.N - 1UL do + (r * 1UL, c * 1UL, int (r + c) % 100) ] + denseMat <- Matrix.fromCoordinateList (Matrix.CoordinateList(uint64 this.N * 1UL, uint64 this.N * 1UL, denseMatData)) + + let sparseMatData = [ + for r in 0UL .. 10UL .. uint64 this.N - 1UL do + for c in 0UL .. 10UL .. uint64 this.N - 1UL do + (r * 1UL, c * 1UL, int (r + c) % 100) ] + sparseMat <- Matrix.fromCoordinateList (Matrix.CoordinateList(uint64 this.N * 1UL, uint64 this.N * 1UL, sparseMatData)) + + [] + member this.VectorForallDense() = + Vector.forall denseVec (fun x -> x > 0) + + [] + member this.VectorForallSparse() = + Vector.forall sparseVec (fun x -> x > 0) + + [] + member this.MatrixForallDense() = + Matrix.forall denseMat (fun x -> x > 0) + + [] + member this.MatrixForallSparse() = + Matrix.forall sparseMat (fun x -> x > 0) diff --git a/QuadTree.Benchmark/Main.fs b/QuadTree.Benchmark/Main.fs index 61394af..3fb2bf4 100644 --- a/QuadTree.Benchmark/Main.fs +++ b/QuadTree.Benchmark/Main.fs @@ -4,7 +4,10 @@ open BenchmarkDotNet.Running let main argv = let benchmarks = BenchmarkSwitcher - [| typeof + [| typeof + typeof + typeof + typeof typeof typeof |] diff --git a/QuadTree.Benchmark/QuadTree.Benchmark.fsproj b/QuadTree.Benchmark/QuadTree.Benchmark.fsproj index 4edb362..ba98b9d 100644 --- a/QuadTree.Benchmark/QuadTree.Benchmark.fsproj +++ b/QuadTree.Benchmark/QuadTree.Benchmark.fsproj @@ -8,6 +8,9 @@ + + + diff --git a/QuadTree.Tests/Tests.Matrix.fs b/QuadTree.Tests/Tests.Matrix.fs index 816b4a2..b65c53d 100644 --- a/QuadTree.Tests/Tests.Matrix.fs +++ b/QuadTree.Tests/Tests.Matrix.fs @@ -642,3 +642,357 @@ let ``Fold sum`` () = let actual = foldAssociative op_add None m1 |> Option.get Assert.Equal(expected, actual) + +[] +let ``Matrix.filter all pass, none changed`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ])) + let actual = Matrix.filter m (fun x -> x > 0) + Assert.Equal(m, actual) + +[] +let ``Matrix.filter none pass, all reset to zero`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ])) + let expected = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, [])) + let actual = Matrix.filter m (fun x -> x < 0) + + Assert.Equal(expected, actual) + +[] +let ``Matrix.filter length is not a power of 2, all reset to zero`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) + (2UL, 0UL, 7) + (2UL, 1UL, 8) + (2UL, 2UL, 9) ])) + let expected = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, [])) + let actual = Matrix.filter m (fun x -> x < 0) + + Assert.Equal(expected, actual) + +[] +let ``Matrix.filter some pass, odd set to zero`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (0UL, 3UL, 4) + (1UL, 0UL, 5) + (1UL, 1UL, 6) + (1UL, 2UL, 7) + (1UL, 3UL, 8) + (2UL, 0UL, 9) + (2UL, 1UL, 10) + (2UL, 2UL, 11) + (2UL, 3UL, 12) + (3UL, 0UL, 13) + (3UL, 1UL, 14) + (3UL, 2UL, 15) + (3UL, 3UL, 16) ])) + let expected = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, + [ (0UL, 1UL, 2) + (0UL, 3UL, 4) + (1UL, 1UL, 6) + (1UL, 3UL, 8) + (2UL, 1UL, 10) + (2UL, 3UL, 12) + (3UL, 1UL, 14) + (3UL, 3UL, 16) ])) + let actual = Matrix.filter m (fun x -> x % 2 = 0) + + Assert.Equal(expected, actual) + +[] +let ``Matrix.filter length is not a power of 2, not all reset to zero`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) + (2UL, 0UL, 7) + (2UL, 1UL, 8) + (2UL, 2UL, 9) ])) + let expected = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, + [ (0UL, 1UL, 2) + (1UL, 0UL, 4) + (1UL, 2UL, 6) + (2UL, 1UL, 8) ])) + let actual = Matrix.filter m (fun x -> x % 2 = 0) + + Assert.Equal(expected, actual) + +[] +let ``Matrix.filter none pass, length is not a power of 2, none changed`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, [])) + let actual = Matrix.filter m (fun x -> x > 0) + Assert.Equal(m, actual) + +[] +let ``Matrix.filter none pass, length is a power of 2, none changed`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, [])) + let actual = Matrix.filter m (fun x -> x > 0) + Assert.Equal(m, actual) + +[] +let ``Matrix.filter single element, passes`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(1UL, 1UL, + [ (0UL, 0UL, 1) ])) + let expected = Matrix.fromCoordinateList ( + CoordinateList(1UL, 1UL, + [ (0UL, 0UL, 1) ])) + let actual = Matrix.filter m (fun x -> x > 0) + + Assert.Equal(expected, actual) + +[] +let ``Matrix.filter single element, fails`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(1UL, 1UL, + [ (0UL, 0UL, 1) ])) + let expected = Matrix.fromCoordinateList ( + CoordinateList(1UL, 1UL, [])) + let actual = Matrix.filter m (fun x -> x < 0) + + Assert.Equal(expected, actual) + +[] +let ``Matrix.exists the first element fits, length is a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ])) + Assert.True (Matrix.exists m (fun x -> x = 1)) + Assert.False (Matrix.exists m (fun x -> x = 10)) + +[] +let ``Matrix.exists the first element fits, length is not a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ])) + Assert.True (Matrix.exists m (fun x -> x = 1)) + +[] +let ``Matrix.exists no element fits, length is a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ])) + Assert.False (Matrix.exists m (fun x -> x = 10)) + +[] +let ``Matrix.exists no element fits, length is not a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ])) + Assert.False (Matrix.exists m (fun x -> x = 10)) + +[] +let ``Matrix.exists empty matrix`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, [])) + Assert.False (Matrix.exists m (fun x -> x = 1)) + +[] +let ``Matrix.exists single element, fits`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(1UL, 1UL, + [ (0UL, 0UL, 1) ])) + Assert.True (Matrix.exists m (fun x -> x = 1)) + +[] +let ``Matrix.exists single element, does not fit`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(1UL, 1UL, + [ (0UL, 0UL, 1) ])) + Assert.False (Matrix.exists m (fun x -> x = 10)) + +[] +let ``Matrix.exists all elements fit,length is a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ])) + Assert.True (Matrix.exists m (fun x -> x > 0)) + +[] +let ``Matrix.exists all elements fit,length is not a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ])) + Assert.True (Matrix.exists m (fun x -> x > 0)) +[] +let ``Matrix.exists some elements fit, length is a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ])) + Assert.True (Matrix.exists m (fun x -> x = 2 || x = 10)) + +[] +let ``Matrix.exists some elements fit, length is not a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ])) + Assert.True (Matrix.exists m (fun x -> x = 2 || x = 10)) + +[] +let ``Matrix.forall the first element fits, length is a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ])) + Assert.True (Matrix.forall m (fun x -> x > 0)) + +[] +let ``Matrix.forall the first element fits, length is not a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ])) + Assert.True (Matrix.forall m (fun x -> x > 0)) + +[] +let ``Matrix.forall no element fits, length is a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ])) + Assert.False (Matrix.forall m (fun x -> x > 10)) + +[] +let ``Matrix.forall no element fits, length is not a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ])) + Assert.False (Matrix.forall m (fun x -> x > 10)) + +[] +let ``Matrix.forall all elements fit,length is a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ])) + Assert.True (Matrix.forall m (fun x -> x > 0)) + +[] +let ``Matrix.forall all elements fit,length is not a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ])) + Assert.True (Matrix.forall m (fun x -> x > 0)) + +[] +let ``Matrix.forall some elements fit, length is a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(4UL, 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ])) + Assert.False (Matrix.forall m (fun x -> x = 2 || x = 10)) + +[] +let ``Matrix.forall some elements fit, length is not a power of 2`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ])) + Assert.False (Matrix.forall m (fun x -> x = 2 || x = 10)) + +[] +let ``Matrix.forall empty matrix`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(3UL, 3UL, [])) + Assert.True (Matrix.forall m (fun x -> x = 1)) + +[] +let ``Matrix.forall single element, fits`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(1UL, 1UL, + [ (0UL, 0UL, 1) ])) + Assert.True (Matrix.forall m (fun x -> x = 1)) + +[] +let ``Matrix.forall single element, does not fit`` () = + let m = Matrix.fromCoordinateList ( + CoordinateList(1UL, 1UL, + [ (0UL, 0UL, 1) ])) + Assert.False (Matrix.forall m (fun x -> x = 10)) \ No newline at end of file diff --git a/QuadTree.Tests/Tests.Vector.fs b/QuadTree.Tests/Tests.Vector.fs index 4eb1af9..6ef2cbd 100644 --- a/QuadTree.Tests/Tests.Vector.fs +++ b/QuadTree.Tests/Tests.Vector.fs @@ -903,3 +903,386 @@ let ``Init vector`` () = let actual = Vector.init 3UL (fun i -> Some(int i)) Assert.Equal(expected, actual) + + +[] +let ``Vector.filter all pass, none changed`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ])) + let actual = Vector.filter v (fun x -> x > 0) + Assert.Equal(v, actual) + +[] +let ``Vector.filter none pass, all reset to zero`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ])) + let expected = + Vector.empty 8UL + let actual = Vector.filter v (fun x -> x < 0) + + Assert.Equal(expected, actual) + +[] +let ``Vector.filter length is not a power of 2, all reset to zero`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ])) + let expected = + Vector.empty 6UL + let actual = Vector.filter v (fun x -> x < 0) + + Assert.Equal(expected, actual) + +[] +let ``Vector.filter some pass, odd set to zero`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ])) + let expected = + Vector.fromCoordinateList ( + CoordinateList(8UL, + [ (1UL, 2) + (3UL, 4) + (5UL, 6) + (7UL, 8) ])) + let actual = Vector.filter v (fun x -> ((x % 2) = 0)) + + Assert.Equal(expected, actual) + +[] +let ``Vector.filter length is not a power of 2, not all reset to zero`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ])) + let expected = + Vector.fromCoordinateList ( + CoordinateList(6UL, + [ (1UL, 2) + (3UL, 4) + (5UL, 6) ])) + let actual = Vector.filter v (fun x -> ((x % 2) = 0)) + + Assert.Equal(expected, actual) + +[] +let ``Vector.filter none pass, length is not a power of 2, none changed`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(6UL, + [])) + let actual = Vector.filter v (fun x -> x > 0) + Assert.Equal(v, actual) + +[] +let ``Vector.filter none pass, length is a power of 2, none changed`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [])) + let actual = Vector.filter v (fun x -> x > 0) + Assert.Equal(v, actual) + +[] +let ``Vector.filter single element, passes`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(1UL, + [ (0UL, 1) ])) + let expected = + Vector.fromCoordinateList ( + CoordinateList(1UL, + [ (0UL, 1) ])) + let actual = Vector.filter v (fun x -> x > 0) + + Assert.Equal(expected, actual) + +[] +let ``Vector.filter single element, fails`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(1UL, + [ (0UL, 1) ])) + let expected = + Vector.empty 1UL + let actual = Vector.filter v (fun x -> x < 0) + + Assert.Equal(expected, actual) + +[] +let ``Vector.exists the first element fits, length is a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ])) + Assert.True (Vector.exists v (fun x -> x > 0)) + +[] +let ``Vector.exists the first element fits, length is not a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ])) + Assert.True (Vector.exists v (fun x -> x > 0)) + +[] +let ``Vector.exists the last item fits, length is a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ])) + Assert.True (Vector.exists v (fun x -> x > 7)) + +[] +let ``Vector.exists the last item fits, length is not a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ])) + Assert.True (Vector.exists v (fun x -> x > 5)) + +[] +let ``Vector.exists empty list`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [])) + Assert.False (Vector.exists v (fun x -> x > 0)) + +[] +let ``Vector.exists no matching elements, length is a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ])) + Assert.False (Vector.exists v (fun x -> x > 8)) + +[] +let ``Vector.exists no matching elements, length is not a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ])) + Assert.False (Vector.exists v (fun x -> x > 6)) + +[] +let ``Vector.exists single element matches`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(1UL, + [ (0UL, 1) ])) + Assert.True (Vector.exists v (fun x -> x = 1)) + +[] +let ``Vector.exists single element does not match`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(1UL, + [ (0UL, 1) ])) + Assert.False (Vector.exists v (fun x -> x = 2)) + +[] +let ``Vector.exists all elements match, length is a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ])) + Assert.True (Vector.exists v (fun x -> x > 0)) + +[] +let ``Vector.exists all elements match, length is not a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ])) + Assert.True (Vector.exists v (fun x -> x > 0)) + +[] +let ``Vector.forall the first element not fits, length is a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ])) + Assert.False (Vector.forall v (fun x -> x > 1)) + +[] +let ``Vector.forall the first element not fits, length is not a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ])) + Assert.False (Vector.forall v (fun x -> x > 1)) + +[] +let ``Vector.forall the last item not fits, length is a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ])) + Assert.False (Vector.forall v (fun x -> x < 8)) + +[] +let ``Vector.forall the last item not fits, length is not a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ])) + Assert.False (Vector.forall v (fun x -> x < 6)) + +[] +let ``Vector.forall empty list`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [])) + Assert.True (Vector.forall v (fun x -> x > 0)) + +[] +let ``Vector.forall no matching elements, length is a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ])) + Assert.False (Vector.forall v (fun x -> x > 8)) + +[] +let ``Vector.forall no matching elements, length is not a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ])) + Assert.False (Vector.forall v (fun x -> x > 6)) + +[] +let ``Vector.forall single element matches`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(1UL, + [ (0UL, 1) ])) + Assert.True (Vector.forall v (fun x -> x = 1)) + +[] +let ``Vector.forall single element does not match`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(1UL, + [ (0UL, 1) ])) + Assert.False (Vector.forall v (fun x -> x = 2)) + +[] +let ``Vector.forall all elements match, length is a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ])) + Assert.True (Vector.forall v (fun x -> x > 0)) + +[] +let ``Vector.forall all elements match, length is not a power of 2`` () = + let v = Vector.fromCoordinateList ( + CoordinateList(6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ])) + Assert.True (Vector.forall v (fun x -> x > 0)) diff --git a/QuadTree/Matrix.fs b/QuadTree/Matrix.fs index 68ea7d7..47bade8 100644 --- a/QuadTree/Matrix.fs +++ b/QuadTree/Matrix.fs @@ -394,3 +394,53 @@ let transpose (matrix: SparseMatrix<_>) = let mask (m1: SparseMatrix<'a>) (m2: SparseMatrix<'b>) f = map2 m1 m2 (fun m1 m2 -> if f m2 then m1 else None) + + +let filter (matrix: SparseMatrix<'a>) (predicate: 'a -> bool) : SparseMatrix<'a> = + let rec inner (prow: uint64) (pcol: uint64) (size: uint64) matrix = + match matrix with + | Node(x1, x2, x3, x4) -> + let halfSize = size / 2UL + + let (nwR, nwC), (neR, neC), (swR, swC), (seR, seC) = + getQuadrantCoords (prow, pcol) (uint64 halfSize) + + let t1, nvals1 = inner nwR nwC halfSize x1 + let t2, nvals2 = inner neR neC halfSize x2 + let t3, nvals3 = inner swR swC halfSize x3 + let t4, nvals4 = inner seR seC halfSize x4 + (mkNode t1 t2 t3 t4), nvals1 + nvals2 + nvals3 + nvals4 + | Leaf(Dummy) -> Leaf(Dummy), 0UL + | Leaf(UserValue(None)) -> Leaf(UserValue(None)), 0UL + | Leaf(UserValue(Some(v))) -> + if predicate v then + Leaf(UserValue(Some v)), (uint64 size) * (uint64 size) * 1UL + else + Leaf(UserValue(None)), 0UL + + let storage, nvals = + inner 0UL 0UL matrix.storage.size matrix.storage.data + + SparseMatrix(matrix.nrows, matrix.ncols, nvals, (Storage(matrix.storage.size, storage))) + +let exists (matrix: SparseMatrix<'a>) (predicate: 'a -> bool) : bool = + let rec inner tree = + match tree with + | Leaf(Dummy) -> false + | Leaf(UserValue(None)) -> false + | Leaf(UserValue(Some(v))) -> predicate v + | Node(nw, ne, sw, se) -> + inner nw || inner ne || inner sw || inner se + + inner matrix.storage.data + +let forall (matrix: SparseMatrix<'a>) (predicate: 'a -> bool) : bool = + let rec inner tree = + match tree with + | Leaf(Dummy) -> true + | Leaf(UserValue(None)) -> true + | Leaf(UserValue(Some(v))) -> predicate v + | Node(nw, ne, sw, se) -> + inner nw && inner ne && inner sw && inner se + + inner matrix.storage.data \ No newline at end of file diff --git a/QuadTree/Vector.fs b/QuadTree/Vector.fs index b7bcb44..6ea0ee4 100644 --- a/QuadTree/Vector.fs +++ b/QuadTree/Vector.fs @@ -353,7 +353,7 @@ let toCoordinateList (vector: SparseVector<'a>) = CoordinateList(length, lst) -let empty length = +let empty (length: uint64) = fromCoordinateList (CoordinateList(length, [])) let foldValues (vector: SparseVector<'a>) (f: 'b -> 'a -> 'b) (state: 'b) = @@ -561,3 +561,41 @@ let scatter | Error x -> Error x) (Ok w) | Error x -> Error Error.InconsistentStructureOfStorages + +let filter (vector: SparseVector<'a>) (predicate: 'a -> bool) : SparseVector<'a> = + let rec inner (size: uint64) vector = + match vector with + | Node(x1, x2) -> + let t1, nvals1 = inner (size / 2UL) x1 + let t2, nvals2 = inner (size / 2UL) x2 + (mkNode t1 t2), nvals1 + nvals2 + | Leaf(Dummy) -> Leaf(Dummy), 0UL + | Leaf(UserValue(None)) -> Leaf(UserValue(None)), 0UL + | Leaf(UserValue(Some(v))) -> + if predicate v then + Leaf(UserValue(Some(v))), (uint64 size) * 1UL + else + Leaf(UserValue(None)), 0UL + + let storage, nvals = inner vector.storage.size vector.storage.data + SparseVector(vector.length, nvals, Storage(vector.storage.size, storage)) + +let exists (vector: SparseVector<'a>) (predicate: 'a -> bool) : bool = + let rec inner vector = + match vector with + | Leaf(Dummy) -> false + | Leaf(UserValue(None)) -> false + | Leaf(UserValue(Some(v))) -> predicate v + | Node(x1, x2) -> inner x1 || inner x2 + + inner vector.storage.data + +let forall (vector: SparseVector<'a>) (predicate: 'a -> bool) : bool = + let rec inner vector = + match vector with + | Leaf(Dummy) -> true + | Leaf(UserValue(None)) -> true + | Leaf(UserValue(Some(v))) -> predicate v + | Node(x1, x2) -> inner x1 && inner x2 + + inner vector.storage.data \ No newline at end of file From bbd6db16db7c8c32da0aabadc95d118e1d305e65 Mon Sep 17 00:00:00 2001 From: Narysev Date: Sat, 13 Jun 2026 11:07:42 +0300 Subject: [PATCH 2/5] fix: formatting --- QuadTree.Benchmark/Exists.fs | 55 ++- QuadTree.Benchmark/Filters.fs | 50 ++- QuadTree.Benchmark/Forall.fs | 57 ++- QuadTree.Tests/Tests.Matrix.fs | 614 +++++++++++++++++++------------- QuadTree.Tests/Tests.Vector.fs | 632 +++++++++++++++++++-------------- QuadTree/Matrix.fs | 10 +- QuadTree/Vector.fs | 4 +- 7 files changed, 854 insertions(+), 568 deletions(-) diff --git a/QuadTree.Benchmark/Exists.fs b/QuadTree.Benchmark/Exists.fs index 2fc08a8..6278303 100644 --- a/QuadTree.Benchmark/Exists.fs +++ b/QuadTree.Benchmark/Exists.fs @@ -12,40 +12,59 @@ type Benchmark() = let mutable denseMat = Unchecked.defaultof> let mutable sparseMat = Unchecked.defaultof> - [] + [] member val N = 0 with get, set [] member this.Setup() = - let denseData = [ for i in 0UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + let denseData = + [ for i in 0UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + denseVec <- Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, denseData)) - let sparseData = [ for i in 0UL .. 10UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] - sparseVec <- Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, sparseData)) + let sparseData = + [ for i in 0UL .. 10UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + + sparseVec <- + Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, sparseData)) + + let denseMatData = + [ for r in 0UL .. uint64 this.N - 1UL do + for c in 0UL .. uint64 this.N - 1UL do + (r * 1UL, c * 1UL, int (r + c) % 100) ] + + denseMat <- + Matrix.fromCoordinateList ( + Matrix.CoordinateList( + uint64 this.N * 1UL, + uint64 this.N * 1UL, + denseMatData + ) + ) - let denseMatData = [ - for r in 0UL .. uint64 this.N - 1UL do - for c in 0UL .. uint64 this.N - 1UL do - (r * 1UL, c * 1UL, int (r + c) % 100) ] - denseMat <- Matrix.fromCoordinateList (Matrix.CoordinateList(uint64 this.N * 1UL, uint64 this.N * 1UL, denseMatData)) + let sparseMatData = + [ for r in 0UL .. 10UL .. uint64 this.N - 1UL do + for c in 0UL .. 10UL .. uint64 this.N - 1UL do + (r * 1UL, c * 1UL, int (r + c) % 100) ] - let sparseMatData = [ - for r in 0UL .. 10UL .. uint64 this.N - 1UL do - for c in 0UL .. 10UL .. uint64 this.N - 1UL do - (r * 1UL, c * 1UL, int (r + c) % 100) ] - sparseMat <- Matrix.fromCoordinateList (Matrix.CoordinateList(uint64 this.N * 1UL, uint64 this.N * 1UL, sparseMatData)) + sparseMat <- + Matrix.fromCoordinateList ( + Matrix.CoordinateList( + uint64 this.N * 1UL, + uint64 this.N * 1UL, + sparseMatData + ) + ) [] - member this.VectorExistsDense() = - Vector.exists denseVec (fun x -> x < 0) + member this.VectorExistsDense() = Vector.exists denseVec (fun x -> x < 0) [] member this.VectorExistsSparse() = Vector.exists sparseVec (fun x -> x < 0) [] - member this.MatrixExistsDense() = - Matrix.exists denseMat (fun x -> x < 0) + member this.MatrixExistsDense() = Matrix.exists denseMat (fun x -> x < 0) [] member this.MatrixExistsSparse() = diff --git a/QuadTree.Benchmark/Filters.fs b/QuadTree.Benchmark/Filters.fs index 075e339..b733fa4 100644 --- a/QuadTree.Benchmark/Filters.fs +++ b/QuadTree.Benchmark/Filters.fs @@ -11,29 +11,49 @@ type Benchmark() = let mutable denseMat = Unchecked.defaultof> let mutable sparseMat = Unchecked.defaultof> - // [] - [] + [] member val N = 0 with get, set [] member this.Setup() = - let denseData = [ for i in 0UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + let denseData = + [ for i in 0UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + denseVec <- Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, denseData)) - let sparseData = [ for i in 0UL .. 10UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] - sparseVec <- Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, sparseData)) + let sparseData = + [ for i in 0UL .. 10UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + + sparseVec <- + Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, sparseData)) + + let denseMatData = + [ for r in 0UL .. uint64 this.N - 1UL do + for c in 0UL .. uint64 this.N - 1UL do + (r * 1UL, c * 1UL, int (r + c) % 100) ] + + denseMat <- + Matrix.fromCoordinateList ( + Matrix.CoordinateList( + uint64 this.N * 1UL, + uint64 this.N * 1UL, + denseMatData + ) + ) - let denseMatData = [ - for r in 0UL .. uint64 this.N - 1UL do - for c in 0UL .. uint64 this.N - 1UL do - (r * 1UL, c * 1UL, int (r + c) % 100) ] - denseMat <- Matrix.fromCoordinateList (Matrix.CoordinateList(uint64 this.N * 1UL, uint64 this.N * 1UL, denseMatData)) + let sparseMatData = + [ for r in 0UL .. 10UL .. uint64 this.N - 1UL do + for c in 0UL .. 10UL .. uint64 this.N - 1UL do + (r * 1UL, c * 1UL, int (r + c) % 100) ] - let sparseMatData = [ - for r in 0UL .. 10UL .. uint64 this.N - 1UL do - for c in 0UL .. 10UL .. uint64 this.N - 1UL do - (r * 1UL, c * 1UL, int (r + c) % 100) ] - sparseMat <- Matrix.fromCoordinateList (Matrix.CoordinateList(uint64 this.N * 1UL, uint64 this.N * 1UL, sparseMatData)) + sparseMat <- + Matrix.fromCoordinateList ( + Matrix.CoordinateList( + uint64 this.N * 1UL, + uint64 this.N * 1UL, + sparseMatData + ) + ) [] member this.VectorFilterDense() = diff --git a/QuadTree.Benchmark/Forall.fs b/QuadTree.Benchmark/Forall.fs index 8d8ad46..af40d24 100644 --- a/QuadTree.Benchmark/Forall.fs +++ b/QuadTree.Benchmark/Forall.fs @@ -12,41 +12,62 @@ type Benchmark() = let mutable denseMat = Unchecked.defaultof> let mutable sparseMat = Unchecked.defaultof> - [] + [] member val N = 0 with get, set [] member this.Setup() = - let denseData = [ for i in 0UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + let denseData = + [ for i in 0UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + denseVec <- Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, denseData)) - let sparseData = [ for i in 0UL .. 10UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] - sparseVec <- Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, sparseData)) + let sparseData = + [ for i in 0UL .. 10UL .. uint64 this.N - 1UL -> (i * 1UL, int i % 100) ] + + sparseVec <- + Vector.fromCoordinateList (Vector.CoordinateList(uint64 this.N * 1UL, sparseData)) + + let denseMatData = + [ for r in 0UL .. uint64 this.N - 1UL do + for c in 0UL .. uint64 this.N - 1UL do + (r * 1UL, c * 1UL, int (r + c) % 100) ] + + denseMat <- + Matrix.fromCoordinateList ( + Matrix.CoordinateList( + uint64 this.N * 1UL, + uint64 this.N * 1UL, + denseMatData + ) + ) - let denseMatData = [ - for r in 0UL .. uint64 this.N - 1UL do - for c in 0UL .. uint64 this.N - 1UL do - (r * 1UL, c * 1UL, int (r + c) % 100) ] - denseMat <- Matrix.fromCoordinateList (Matrix.CoordinateList(uint64 this.N * 1UL, uint64 this.N * 1UL, denseMatData)) + let sparseMatData = + [ for r in 0UL .. 10UL .. uint64 this.N - 1UL do + for c in 0UL .. 10UL .. uint64 this.N - 1UL do + (r * 1UL, c * 1UL, int (r + c) % 100) ] - let sparseMatData = [ - for r in 0UL .. 10UL .. uint64 this.N - 1UL do - for c in 0UL .. 10UL .. uint64 this.N - 1UL do - (r * 1UL, c * 1UL, int (r + c) % 100) ] - sparseMat <- Matrix.fromCoordinateList (Matrix.CoordinateList(uint64 this.N * 1UL, uint64 this.N * 1UL, sparseMatData)) + sparseMat <- + Matrix.fromCoordinateList ( + Matrix.CoordinateList( + uint64 this.N * 1UL, + uint64 this.N * 1UL, + sparseMatData + ) + ) [] member this.VectorForallDense() = - Vector.forall denseVec (fun x -> x > 0) + Vector.forall denseVec (fun x -> x >= 0) [] member this.VectorForallSparse() = - Vector.forall sparseVec (fun x -> x > 0) + Vector.forall sparseVec (fun x -> x >= 0) [] member this.MatrixForallDense() = - Matrix.forall denseMat (fun x -> x > 0) + Matrix.forall denseMat (fun x -> x >= 0) [] member this.MatrixForallSparse() = - Matrix.forall sparseMat (fun x -> x > 0) + Matrix.forall sparseMat (fun x -> x >= 0) diff --git a/QuadTree.Tests/Tests.Matrix.fs b/QuadTree.Tests/Tests.Matrix.fs index b65c53d..e0192a8 100644 --- a/QuadTree.Tests/Tests.Matrix.fs +++ b/QuadTree.Tests/Tests.Matrix.fs @@ -645,354 +645,492 @@ let ``Fold sum`` () = [] let ``Matrix.filter all pass, none changed`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ])) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + ) + let actual = Matrix.filter m (fun x -> x > 0) Assert.Equal(m, actual) [] let ``Matrix.filter none pass, all reset to zero`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ])) - let expected = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, [])) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + ) + + let expected = + Matrix.fromCoordinateList (CoordinateList(4UL, 4UL, [])) + let actual = Matrix.filter m (fun x -> x < 0) Assert.Equal(expected, actual) [] let ``Matrix.filter length is not a power of 2, all reset to zero`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) - (2UL, 0UL, 7) - (2UL, 1UL, 8) - (2UL, 2UL, 9) ])) - let expected = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, [])) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) + (2UL, 0UL, 7) + (2UL, 1UL, 8) + (2UL, 2UL, 9) ] + ) + ) + + let expected = + Matrix.fromCoordinateList (CoordinateList(3UL, 3UL, [])) + let actual = Matrix.filter m (fun x -> x < 0) Assert.Equal(expected, actual) [] let ``Matrix.filter some pass, odd set to zero`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (0UL, 3UL, 4) - (1UL, 0UL, 5) - (1UL, 1UL, 6) - (1UL, 2UL, 7) - (1UL, 3UL, 8) - (2UL, 0UL, 9) - (2UL, 1UL, 10) - (2UL, 2UL, 11) - (2UL, 3UL, 12) - (3UL, 0UL, 13) - (3UL, 1UL, 14) - (3UL, 2UL, 15) - (3UL, 3UL, 16) ])) - let expected = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, - [ (0UL, 1UL, 2) - (0UL, 3UL, 4) - (1UL, 1UL, 6) - (1UL, 3UL, 8) - (2UL, 1UL, 10) - (2UL, 3UL, 12) - (3UL, 1UL, 14) - (3UL, 3UL, 16) ])) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (0UL, 3UL, 4) + (1UL, 0UL, 5) + (1UL, 1UL, 6) + (1UL, 2UL, 7) + (1UL, 3UL, 8) + (2UL, 0UL, 9) + (2UL, 1UL, 10) + (2UL, 2UL, 11) + (2UL, 3UL, 12) + (3UL, 0UL, 13) + (3UL, 1UL, 14) + (3UL, 2UL, 15) + (3UL, 3UL, 16) ] + ) + ) + + let expected = + Matrix.fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 1UL, 2) + (0UL, 3UL, 4) + (1UL, 1UL, 6) + (1UL, 3UL, 8) + (2UL, 1UL, 10) + (2UL, 3UL, 12) + (3UL, 1UL, 14) + (3UL, 3UL, 16) ] + ) + ) + let actual = Matrix.filter m (fun x -> x % 2 = 0) Assert.Equal(expected, actual) [] let ``Matrix.filter length is not a power of 2, not all reset to zero`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) - (2UL, 0UL, 7) - (2UL, 1UL, 8) - (2UL, 2UL, 9) ])) - let expected = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, - [ (0UL, 1UL, 2) - (1UL, 0UL, 4) - (1UL, 2UL, 6) - (2UL, 1UL, 8) ])) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) + (2UL, 0UL, 7) + (2UL, 1UL, 8) + (2UL, 2UL, 9) ] + ) + ) + + let expected = + Matrix.fromCoordinateList ( + CoordinateList( + 3UL, + 3UL, + [ (0UL, 1UL, 2) + (1UL, 0UL, 4) + (1UL, 2UL, 6) + (2UL, 1UL, 8) ] + ) + ) + let actual = Matrix.filter m (fun x -> x % 2 = 0) Assert.Equal(expected, actual) [] let ``Matrix.filter none pass, length is not a power of 2, none changed`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, [])) + let m = Matrix.fromCoordinateList (CoordinateList(3UL, 3UL, [])) let actual = Matrix.filter m (fun x -> x > 0) Assert.Equal(m, actual) [] let ``Matrix.filter none pass, length is a power of 2, none changed`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, [])) + let m = Matrix.fromCoordinateList (CoordinateList(4UL, 4UL, [])) let actual = Matrix.filter m (fun x -> x > 0) Assert.Equal(m, actual) [] let ``Matrix.filter single element, passes`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(1UL, 1UL, - [ (0UL, 0UL, 1) ])) - let expected = Matrix.fromCoordinateList ( - CoordinateList(1UL, 1UL, - [ (0UL, 0UL, 1) ])) + let m = + Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) + + let expected = + Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) + let actual = Matrix.filter m (fun x -> x > 0) Assert.Equal(expected, actual) [] let ``Matrix.filter single element, fails`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(1UL, 1UL, - [ (0UL, 0UL, 1) ])) - let expected = Matrix.fromCoordinateList ( - CoordinateList(1UL, 1UL, [])) + let m = + Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) + + let expected = + Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [])) + let actual = Matrix.filter m (fun x -> x < 0) Assert.Equal(expected, actual) [] let ``Matrix.exists the first element fits, length is a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ])) - Assert.True (Matrix.exists m (fun x -> x = 1)) - Assert.False (Matrix.exists m (fun x -> x = 10)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + ) + + Assert.True(Matrix.exists m (fun x -> x = 1)) + Assert.False(Matrix.exists m (fun x -> x = 10)) [] let ``Matrix.exists the first element fits, length is not a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ])) - Assert.True (Matrix.exists m (fun x -> x = 1)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ] + ) + ) + + Assert.True(Matrix.exists m (fun x -> x = 1)) [] let ``Matrix.exists no element fits, length is a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ])) - Assert.False (Matrix.exists m (fun x -> x = 10)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + ) + + Assert.False(Matrix.exists m (fun x -> x = 10)) [] let ``Matrix.exists no element fits, length is not a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ])) - Assert.False (Matrix.exists m (fun x -> x = 10)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ] + ) + ) + + Assert.False(Matrix.exists m (fun x -> x = 10)) [] let ``Matrix.exists empty matrix`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, [])) - Assert.False (Matrix.exists m (fun x -> x = 1)) + let m = Matrix.fromCoordinateList (CoordinateList(3UL, 3UL, [])) + Assert.False(Matrix.exists m (fun x -> x = 1)) [] let ``Matrix.exists single element, fits`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(1UL, 1UL, - [ (0UL, 0UL, 1) ])) - Assert.True (Matrix.exists m (fun x -> x = 1)) + let m = + Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) + + Assert.True(Matrix.exists m (fun x -> x = 1)) [] let ``Matrix.exists single element, does not fit`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(1UL, 1UL, - [ (0UL, 0UL, 1) ])) - Assert.False (Matrix.exists m (fun x -> x = 10)) + let m = + Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) + + Assert.False(Matrix.exists m (fun x -> x = 10)) [] let ``Matrix.exists all elements fit,length is a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ])) - Assert.True (Matrix.exists m (fun x -> x > 0)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + ) + + Assert.True(Matrix.exists m (fun x -> x > 0)) [] let ``Matrix.exists all elements fit,length is not a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ])) - Assert.True (Matrix.exists m (fun x -> x > 0)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ] + ) + ) + + Assert.True(Matrix.exists m (fun x -> x > 0)) + [] let ``Matrix.exists some elements fit, length is a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ])) - Assert.True (Matrix.exists m (fun x -> x = 2 || x = 10)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + ) + + Assert.True(Matrix.exists m (fun x -> x = 2 || x = 10)) [] let ``Matrix.exists some elements fit, length is not a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ])) - Assert.True (Matrix.exists m (fun x -> x = 2 || x = 10)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ] + ) + ) + + Assert.True(Matrix.exists m (fun x -> x = 2 || x = 10)) [] let ``Matrix.forall the first element fits, length is a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ])) - Assert.True (Matrix.forall m (fun x -> x > 0)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + ) + + Assert.True(Matrix.forall m (fun x -> x > 0)) [] let ``Matrix.forall the first element fits, length is not a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ])) - Assert.True (Matrix.forall m (fun x -> x > 0)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ] + ) + ) + + Assert.True(Matrix.forall m (fun x -> x > 0)) [] let ``Matrix.forall no element fits, length is a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ])) - Assert.False (Matrix.forall m (fun x -> x > 10)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + ) + + Assert.False(Matrix.forall m (fun x -> x > 10)) [] let ``Matrix.forall no element fits, length is not a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ])) - Assert.False (Matrix.forall m (fun x -> x > 10)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ] + ) + ) + + Assert.False(Matrix.forall m (fun x -> x > 10)) [] let ``Matrix.forall all elements fit,length is a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ])) - Assert.True (Matrix.forall m (fun x -> x > 0)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + ) + + Assert.True(Matrix.forall m (fun x -> x > 0)) [] let ``Matrix.forall all elements fit,length is not a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ])) - Assert.True (Matrix.forall m (fun x -> x > 0)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ] + ) + ) + + Assert.True(Matrix.forall m (fun x -> x > 0)) [] let ``Matrix.forall some elements fit, length is a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(4UL, 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ])) - Assert.False (Matrix.forall m (fun x -> x = 2 || x = 10)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + ) + + Assert.False(Matrix.forall m (fun x -> x = 2 || x = 10)) [] let ``Matrix.forall some elements fit, length is not a power of 2`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ])) - Assert.False (Matrix.forall m (fun x -> x = 2 || x = 10)) + let m = + Matrix.fromCoordinateList ( + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (0UL, 2UL, 3) + (1UL, 0UL, 4) + (1UL, 1UL, 5) + (1UL, 2UL, 6) ] + ) + ) + + Assert.False(Matrix.forall m (fun x -> x = 2 || x = 10)) [] let ``Matrix.forall empty matrix`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(3UL, 3UL, [])) - Assert.True (Matrix.forall m (fun x -> x = 1)) + let m = Matrix.fromCoordinateList (CoordinateList(3UL, 3UL, [])) + Assert.True(Matrix.forall m (fun x -> x = 1)) [] let ``Matrix.forall single element, fits`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(1UL, 1UL, - [ (0UL, 0UL, 1) ])) - Assert.True (Matrix.forall m (fun x -> x = 1)) + let m = + Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) + + Assert.True(Matrix.forall m (fun x -> x = 1)) [] let ``Matrix.forall single element, does not fit`` () = - let m = Matrix.fromCoordinateList ( - CoordinateList(1UL, 1UL, - [ (0UL, 0UL, 1) ])) - Assert.False (Matrix.forall m (fun x -> x = 10)) \ No newline at end of file + let m = + Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) + + Assert.False(Matrix.forall m (fun x -> x = 10)) diff --git a/QuadTree.Tests/Tests.Vector.fs b/QuadTree.Tests/Tests.Vector.fs index 6ef2cbd..9349656 100644 --- a/QuadTree.Tests/Tests.Vector.fs +++ b/QuadTree.Tests/Tests.Vector.fs @@ -907,382 +907,472 @@ let ``Init vector`` () = [] let ``Vector.filter all pass, none changed`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) - (6UL, 7) - (7UL, 8) ])) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ] + ) + ) + let actual = Vector.filter v (fun x -> x > 0) Assert.Equal(v, actual) [] let ``Vector.filter none pass, all reset to zero`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) - (6UL, 7) - (7UL, 8) ])) - let expected = - Vector.empty 8UL + let v = + Vector.fromCoordinateList ( + CoordinateList( + 8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ] + ) + ) + + let expected = Vector.empty 8UL let actual = Vector.filter v (fun x -> x < 0) - + Assert.Equal(expected, actual) [] let ``Vector.filter length is not a power of 2, all reset to zero`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(6UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) ])) - let expected = - Vector.empty 6UL + let v = + Vector.fromCoordinateList ( + CoordinateList( + 6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ] + ) + ) + + let expected = Vector.empty 6UL let actual = Vector.filter v (fun x -> x < 0) Assert.Equal(expected, actual) [] let ``Vector.filter some pass, odd set to zero`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) - (6UL, 7) - (7UL, 8) ])) - let expected = + let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [ (1UL, 2) + CoordinateList( + 8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) (3UL, 4) + (4UL, 5) (5UL, 6) - (7UL, 8) ])) + (6UL, 7) + (7UL, 8) ] + ) + ) + + let expected = + Vector.fromCoordinateList ( + CoordinateList(8UL, [ (1UL, 2); (3UL, 4); (5UL, 6); (7UL, 8) ]) + ) + let actual = Vector.filter v (fun x -> ((x % 2) = 0)) - + Assert.Equal(expected, actual) [] let ``Vector.filter length is not a power of 2, not all reset to zero`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(6UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) ])) - let expected = + let v = Vector.fromCoordinateList ( - CoordinateList(6UL, - [ (1UL, 2) + CoordinateList( + 6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) (3UL, 4) - (5UL, 6) ])) + (4UL, 5) + (5UL, 6) ] + ) + ) + + let expected = + Vector.fromCoordinateList ( + CoordinateList(6UL, [ (1UL, 2); (3UL, 4); (5UL, 6) ]) + ) + let actual = Vector.filter v (fun x -> ((x % 2) = 0)) - + Assert.Equal(expected, actual) [] let ``Vector.filter none pass, length is not a power of 2, none changed`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(6UL, - [])) + let v = Vector.fromCoordinateList (CoordinateList(6UL, [])) let actual = Vector.filter v (fun x -> x > 0) Assert.Equal(v, actual) - + [] let ``Vector.filter none pass, length is a power of 2, none changed`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [])) + let v = Vector.fromCoordinateList (CoordinateList(8UL, [])) let actual = Vector.filter v (fun x -> x > 0) Assert.Equal(v, actual) - + [] let ``Vector.filter single element, passes`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(1UL, - [ (0UL, 1) ])) + let v = + Vector.fromCoordinateList (CoordinateList(1UL, [ (0UL, 1) ])) + let expected = - Vector.fromCoordinateList ( - CoordinateList(1UL, - [ (0UL, 1) ])) + Vector.fromCoordinateList (CoordinateList(1UL, [ (0UL, 1) ])) + let actual = Vector.filter v (fun x -> x > 0) - + Assert.Equal(expected, actual) [] let ``Vector.filter single element, fails`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(1UL, - [ (0UL, 1) ])) - let expected = - Vector.empty 1UL + let v = + Vector.fromCoordinateList (CoordinateList(1UL, [ (0UL, 1) ])) + + let expected = Vector.empty 1UL let actual = Vector.filter v (fun x -> x < 0) Assert.Equal(expected, actual) [] let ``Vector.exists the first element fits, length is a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) - (6UL, 7) - (7UL, 8) ])) - Assert.True (Vector.exists v (fun x -> x > 0)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ] + ) + ) + + Assert.True(Vector.exists v (fun x -> x > 0)) [] let ``Vector.exists the first element fits, length is not a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(6UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) ])) - Assert.True (Vector.exists v (fun x -> x > 0)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ] + ) + ) + + Assert.True(Vector.exists v (fun x -> x > 0)) [] let ``Vector.exists the last item fits, length is a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) - (6UL, 7) - (7UL, 8) ])) - Assert.True (Vector.exists v (fun x -> x > 7)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ] + ) + ) + + Assert.True(Vector.exists v (fun x -> x > 7)) [] let ``Vector.exists the last item fits, length is not a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(6UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) ])) - Assert.True (Vector.exists v (fun x -> x > 5)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ] + ) + ) + + Assert.True(Vector.exists v (fun x -> x > 5)) [] let ``Vector.exists empty list`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [])) - Assert.False (Vector.exists v (fun x -> x > 0)) + let v = Vector.fromCoordinateList (CoordinateList(8UL, [])) + Assert.False(Vector.exists v (fun x -> x > 0)) [] let ``Vector.exists no matching elements, length is a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) - (6UL, 7) - (7UL, 8) ])) - Assert.False (Vector.exists v (fun x -> x > 8)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ] + ) + ) + + Assert.False(Vector.exists v (fun x -> x > 8)) [] let ``Vector.exists no matching elements, length is not a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(6UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) ])) - Assert.False (Vector.exists v (fun x -> x > 6)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ] + ) + ) + + Assert.False(Vector.exists v (fun x -> x > 6)) [] let ``Vector.exists single element matches`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(1UL, - [ (0UL, 1) ])) - Assert.True (Vector.exists v (fun x -> x = 1)) + let v = + Vector.fromCoordinateList (CoordinateList(1UL, [ (0UL, 1) ])) + + Assert.True(Vector.exists v (fun x -> x = 1)) [] let ``Vector.exists single element does not match`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(1UL, - [ (0UL, 1) ])) - Assert.False (Vector.exists v (fun x -> x = 2)) + let v = + Vector.fromCoordinateList (CoordinateList(1UL, [ (0UL, 1) ])) + + Assert.False(Vector.exists v (fun x -> x = 2)) [] let ``Vector.exists all elements match, length is a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) - (6UL, 7) - (7UL, 8) ])) - Assert.True (Vector.exists v (fun x -> x > 0)) - -[] -let ``Vector.exists all elements match, length is not a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(6UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) ])) - Assert.True (Vector.exists v (fun x -> x > 0)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ] + ) + ) + + Assert.True(Vector.exists v (fun x -> x > 0)) + +[] +let ``Vector.exists all elements match, length is not a power of 2`` () = + let v = + Vector.fromCoordinateList ( + CoordinateList( + 6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ] + ) + ) + + Assert.True(Vector.exists v (fun x -> x > 0)) [] let ``Vector.forall the first element not fits, length is a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) - (6UL, 7) - (7UL, 8) ])) - Assert.False (Vector.forall v (fun x -> x > 1)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ] + ) + ) + + Assert.False(Vector.forall v (fun x -> x > 1)) [] let ``Vector.forall the first element not fits, length is not a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(6UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) ])) - Assert.False (Vector.forall v (fun x -> x > 1)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ] + ) + ) + + Assert.False(Vector.forall v (fun x -> x > 1)) [] let ``Vector.forall the last item not fits, length is a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) - (6UL, 7) - (7UL, 8) ])) - Assert.False (Vector.forall v (fun x -> x < 8)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ] + ) + ) + + Assert.False(Vector.forall v (fun x -> x < 8)) [] let ``Vector.forall the last item not fits, length is not a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(6UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) ])) - Assert.False (Vector.forall v (fun x -> x < 6)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ] + ) + ) + + Assert.False(Vector.forall v (fun x -> x < 6)) [] let ``Vector.forall empty list`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [])) - Assert.True (Vector.forall v (fun x -> x > 0)) + let v = Vector.fromCoordinateList (CoordinateList(8UL, [])) + Assert.True(Vector.forall v (fun x -> x > 0)) [] let ``Vector.forall no matching elements, length is a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) - (6UL, 7) - (7UL, 8) ])) - Assert.False (Vector.forall v (fun x -> x > 8)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ] + ) + ) + + Assert.False(Vector.forall v (fun x -> x > 8)) [] let ``Vector.forall no matching elements, length is not a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(6UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) ])) - Assert.False (Vector.forall v (fun x -> x > 6)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ] + ) + ) + + Assert.False(Vector.forall v (fun x -> x > 6)) [] let ``Vector.forall single element matches`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(1UL, - [ (0UL, 1) ])) - Assert.True (Vector.forall v (fun x -> x = 1)) + let v = + Vector.fromCoordinateList (CoordinateList(1UL, [ (0UL, 1) ])) + + Assert.True(Vector.forall v (fun x -> x = 1)) [] let ``Vector.forall single element does not match`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(1UL, - [ (0UL, 1) ])) - Assert.False (Vector.forall v (fun x -> x = 2)) + let v = + Vector.fromCoordinateList (CoordinateList(1UL, [ (0UL, 1) ])) + + Assert.False(Vector.forall v (fun x -> x = 2)) [] let ``Vector.forall all elements match, length is a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(8UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) - (6UL, 7) - (7UL, 8) ])) - Assert.True (Vector.forall v (fun x -> x > 0)) - -[] -let ``Vector.forall all elements match, length is not a power of 2`` () = - let v = Vector.fromCoordinateList ( - CoordinateList(6UL, - [ (0UL, 1) - (1UL, 2) - (2UL, 3) - (3UL, 4) - (4UL, 5) - (5UL, 6) ])) - Assert.True (Vector.forall v (fun x -> x > 0)) + let v = + Vector.fromCoordinateList ( + CoordinateList( + 8UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) + (6UL, 7) + (7UL, 8) ] + ) + ) + + Assert.True(Vector.forall v (fun x -> x > 0)) + +[] +let ``Vector.forall all elements match, length is not a power of 2`` () = + let v = + Vector.fromCoordinateList ( + CoordinateList( + 6UL, + [ (0UL, 1) + (1UL, 2) + (2UL, 3) + (3UL, 4) + (4UL, 5) + (5UL, 6) ] + ) + ) + + Assert.True(Vector.forall v (fun x -> x > 0)) diff --git a/QuadTree/Matrix.fs b/QuadTree/Matrix.fs index 47bade8..e78adf7 100644 --- a/QuadTree/Matrix.fs +++ b/QuadTree/Matrix.fs @@ -417,7 +417,7 @@ let filter (matrix: SparseMatrix<'a>) (predicate: 'a -> bool) : SparseMatrix<'a> Leaf(UserValue(Some v)), (uint64 size) * (uint64 size) * 1UL else Leaf(UserValue(None)), 0UL - + let storage, nvals = inner 0UL 0UL matrix.storage.size matrix.storage.data @@ -429,8 +429,7 @@ let exists (matrix: SparseMatrix<'a>) (predicate: 'a -> bool) : bool = | Leaf(Dummy) -> false | Leaf(UserValue(None)) -> false | Leaf(UserValue(Some(v))) -> predicate v - | Node(nw, ne, sw, se) -> - inner nw || inner ne || inner sw || inner se + | Node(nw, ne, sw, se) -> inner nw || inner ne || inner sw || inner se inner matrix.storage.data @@ -440,7 +439,6 @@ let forall (matrix: SparseMatrix<'a>) (predicate: 'a -> bool) : bool = | Leaf(Dummy) -> true | Leaf(UserValue(None)) -> true | Leaf(UserValue(Some(v))) -> predicate v - | Node(nw, ne, sw, se) -> - inner nw && inner ne && inner sw && inner se + | Node(nw, ne, sw, se) -> inner nw && inner ne && inner sw && inner se - inner matrix.storage.data \ No newline at end of file + inner matrix.storage.data diff --git a/QuadTree/Vector.fs b/QuadTree/Vector.fs index 6ea0ee4..00e7f2a 100644 --- a/QuadTree/Vector.fs +++ b/QuadTree/Vector.fs @@ -589,7 +589,7 @@ let exists (vector: SparseVector<'a>) (predicate: 'a -> bool) : bool = | Node(x1, x2) -> inner x1 || inner x2 inner vector.storage.data - + let forall (vector: SparseVector<'a>) (predicate: 'a -> bool) : bool = let rec inner vector = match vector with @@ -598,4 +598,4 @@ let forall (vector: SparseVector<'a>) (predicate: 'a -> bool) : bool = | Leaf(UserValue(Some(v))) -> predicate v | Node(x1, x2) -> inner x1 && inner x2 - inner vector.storage.data \ No newline at end of file + inner vector.storage.data From 52f9ae74bf731ab824d138559c31de092aafc14a Mon Sep 17 00:00:00 2001 From: Narysev Date: Thu, 17 Sep 2026 15:04:29 +0300 Subject: [PATCH 3/5] Fix COO map/get/mapi/map/map2 on dense data, add COO tests --- QuadTree.Tests/Tests.COO.fs | 643 ++++++++++++++++++++++++++++++++++++ QuadTree/COO.fs | 284 ++++++++++++++++ 2 files changed, 927 insertions(+) create mode 100644 QuadTree.Tests/Tests.COO.fs create mode 100644 QuadTree/COO.fs diff --git a/QuadTree.Tests/Tests.COO.fs b/QuadTree.Tests/Tests.COO.fs new file mode 100644 index 0000000..2075f94 --- /dev/null +++ b/QuadTree.Tests/Tests.COO.fs @@ -0,0 +1,643 @@ +module COO.Tests + +open System +open Xunit + +open Matrix +open COO +open Common + +let op_add x y = + match (x, y) with + | Some(a), Some(b) -> Some(a + b) + | Some a, None + | None, Some a -> Some a + | _ -> None + +let op_mult x y = + match (x, y) with + | Some(a), Some(b) -> Some(a * b) + | _ -> None + +// === cooGet tests === + +[] +let ``cooGet existing value`` () = + let coo = + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) ] + ) + + let actual = cooGet (coo, 0UL, 1UL) + + Assert.Equal(Ok(Some 2), actual) + +[] +let ``cooGet missing value`` () = + let coo = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 1); (1UL, 1UL, 2) ]) + + let actual = cooGet (coo, 2UL, 2UL) + + Assert.Equal(Ok None, actual) + +[] +let ``cooGet out of bounds`` () = + let coo = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 1) ]) + + let actual = cooGet (coo, 5UL, 5UL) + + Assert.Equal(Error Error.InvalidElementIndex, actual) + +// === cooUpdate tests === + +[] +let ``cooUpdate replaces existing`` () = + let coo = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 1); (1UL, 1UL, 2) ]) + + let expected = + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 99); (1UL, 1UL, 2) ] + ) + + let actual = cooUpdate (coo, 0UL, 0UL, 99) + + Assert.Equal(Ok expected, actual) + +[] +let ``cooUpdate inserts new in middle`` () = + let coo = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 1); (2UL, 2UL, 2) ]) + + let expected = + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1) + (1UL, 1UL, 10) + (2UL, 2UL, 2) ] + ) + + let actual = cooUpdate (coo, 1UL, 1UL, 10) + + Assert.Equal(Ok expected, actual) + +[] +let ``cooUpdate inserts at end`` () = + let coo = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 1) ]) + + let expected = + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1); (3UL, 3UL, 20) ] + ) + + let actual = cooUpdate (coo, 3UL, 3UL, 20) + + Assert.Equal(Ok expected, actual) + +[] +let ``cooUpdate out of bounds`` () = + let coo = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 1) ]) + + let actual = cooUpdate (coo, 5UL, 5UL, 99) + + Assert.Equal(Error Error.InvalidElementIndex, actual) + +// === cooMap tests === + +[] +let ``cooMap doubles values`` () = + let nrows = 4UL + let ncols = 4UL + + let data = + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + |> List.sort + + let coo = CoordinateList(nrows, ncols, data) + + let f v = v |> Option.map (fun v -> v * 2) + + let expected = + CoordinateList( + nrows, + ncols, + [ (0UL, 0UL, 2) + (0UL, 1UL, 4) + (1UL, 0UL, 6) + (1UL, 1UL, 8) ] + ) + + let actual = cooMap coo f + + Assert.Equal(expected, actual) + +[] +let ``cooMap filters None results`` () = + let nrows = 4UL + let ncols = 4UL + + let data = + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + + let coo = CoordinateList(nrows, ncols, data) + + let f v = + v |> Option.bind (fun v -> + match v with + | 1 -> None + | _ -> Some(v * 10)) + + let expected = + CoordinateList( + nrows, + ncols, + [ (0UL, 1UL, 20) + (1UL, 0UL, 30) + (1UL, 1UL, 40) ] + ) + + let actual = cooMap coo f + + Assert.Equal(expected, actual) + +[] +let ``cooMap fills missing cells (general form)`` () = + let nrows = 3UL + let ncols = 3UL + + let data = [ (0UL, 0UL, 1); (2UL, 2UL, 5) ] + + let coo = CoordinateList(nrows, ncols, data) + + let f v = Some(defaultArg v 0) + + let actual = cooMap coo f + + Assert.Equal(nrows, actual.nrows) + Assert.Equal(ncols, actual.ncols) + Assert.Equal(9, actual.list.Length) + Assert.Equal(List.tryFind (fun (i, j, _) -> i = 2UL && j = 2UL) actual.list, + Some(2UL, 2UL, 5)) + +[] +let ``cooMap zero-size matrix`` () = + let coo = CoordinateList(0UL, 0UL, []) + let f v = v |> Option.map (fun v -> v * 2) + let actual = cooMap coo f + let expected = CoordinateList(0UL, 0UL, []) + Assert.Equal(expected, actual) + +// === cooMap2 tests === + +[] +let ``cooMap2 addition`` () = + let nrows = 10UL + let ncols = 12UL + + let d1 = + [ (0UL, 3UL, 4) + (3UL, 11UL, 2) + (9UL, 2UL, 5) ] + |> List.sort + + let d2 = + [ (0UL, 3UL, 6) + (3UL, 3UL, 33) + (3UL, 11UL, -1) ] + |> List.sort + + let f x y = + match x, y with + | Some a, Some b -> Some(a + b) + | Some a, None -> Some a + | None, Some b -> Some b + | _ -> None + + let expected = + CoordinateList( + nrows, + ncols, + [ (0UL, 3UL, 10) + (3UL, 3UL, 33) + (9UL, 2UL, 5) + (3UL, 11UL, 1) ] + |> List.sort + ) + + let c1 = CoordinateList(nrows, ncols, d1) + let c2 = CoordinateList(nrows, ncols, d2) + + let actual = cooMap2 c1 c2 f + + Assert.Equal(expected, actual) + +[] +let ``cooMap2 with mismatched positions`` () = + let nrows = 4UL + let ncols = 4UL + + let d1 = [ (0UL, 0UL, 1); (2UL, 2UL, 3) ] + + let d2 = [ (1UL, 1UL, 10); (3UL, 3UL, 30) ] + + let f x y = + match x, y with + | Some a, Some b -> Some(a + b) + | Some a, None -> Some(a + 100) + | None, Some b -> Some(b + 200) + | _ -> None + + let expected = + CoordinateList( + nrows, + ncols, + [ (0UL, 0UL, 101) + (1UL, 1UL, 210) + (2UL, 2UL, 103) + (3UL, 3UL, 230) ] + ) + + let c1 = CoordinateList(nrows, ncols, d1) + let c2 = CoordinateList(nrows, ncols, d2) + + let actual = cooMap2 c1 c2 f + + Assert.Equal(expected, actual) + +[] +let ``cooMap2 dense filters None from existing entries`` () = + let nrows = 4UL + let ncols = 4UL + + let d1 = [ (0UL, 0UL, 1); (1UL, 1UL, 2) ] + let d2 = [ (0UL, 0UL, 10); (2UL, 2UL, 30) ] + + let f x y = + match x, y with + | Some a, Some b when a + b > 5 -> None + | Some a, Some b -> Some(a + b) + | Some a, None -> Some a + | None, Some b -> Some b + | None, None -> Some 0 + + let expected = + CoordinateList( + nrows, + ncols, + [ (0UL, 1UL, 0) + (0UL, 2UL, 0) + (0UL, 3UL, 0) + (1UL, 0UL, 0) + (1UL, 1UL, 2) + (1UL, 2UL, 0) + (1UL, 3UL, 0) + (2UL, 0UL, 0) + (2UL, 1UL, 0) + (2UL, 2UL, 30) + (2UL, 3UL, 0) + (3UL, 0UL, 0) + (3UL, 1UL, 0) + (3UL, 2UL, 0) + (3UL, 3UL, 0) ] + ) + + let c1 = CoordinateList(nrows, ncols, d1) + let c2 = CoordinateList(nrows, ncols, d2) + + let actual = cooMap2 c1 c2 f + + Assert.Equal(expected, actual) + +// === cooMapi tests === + +[] +let ``cooMapi position-dependent values`` () = + let nrows = 4UL + let ncols = 4UL + + let data = + [ (0UL, 0UL, 1) + (1UL, 1UL, 2) + (2UL, 3UL, 3) ] + |> List.sort + + let coo = CoordinateList(nrows, ncols, data) + + let f i j v = v |> Option.map (fun v -> v + (int (uint64 i))) + + let expected = + CoordinateList( + nrows, + ncols, + [ (0UL, 0UL, 1) + (1UL, 1UL, 3) + (2UL, 3UL, 5) ] + ) + + let actual = cooMapi coo f + + Assert.Equal(expected, actual) + +[] +let ``cooMapi filters None results`` () = + let nrows = 4UL + let ncols = 4UL + + let data = + [ (0UL, 0UL, 1) + (0UL, 1UL, 5) + (1UL, 0UL, 3) ] + + let coo = CoordinateList(nrows, ncols, data) + + let f _i _j v = v |> Option.bind (fun v -> if v > 2 then Some(v * 10) else None) + + let expected = + CoordinateList( + nrows, + ncols, + [ (0UL, 1UL, 50) + (1UL, 0UL, 30) ] + ) + + let actual = cooMapi coo f + + Assert.Equal(expected, actual) + +[] +let ``cooMapi empty input`` () = + let coo = CoordinateList(4UL, 4UL, []) + let f _i _j v = v |> Option.map (fun v -> v * 2) + let actual = cooMapi coo f + let expected = CoordinateList(4UL, 4UL, []) + Assert.Equal(expected, actual) + +[] +let ``cooMapi fills missing cells (general form)`` () = + let nrows = 3UL + let ncols = 3UL + + let data = [ (0UL, 0UL, 1); (2UL, 2UL, 5) ] + let coo = CoordinateList(nrows, ncols, data) + + let f _i _j v = Some(defaultArg v 0) + + let actual = cooMapi coo f + + Assert.Equal(nrows, actual.nrows) + Assert.Equal(ncols, actual.ncols) + Assert.Equal(9, actual.list.Length) + Assert.Equal(List.tryFind (fun (i, j, _) -> i = 2UL && j = 2UL) actual.list, + Some(2UL, 2UL, 5)) + +[] +let ``cooMapi position-dependent fill of missing cells`` () = + let nrows = 2UL + let ncols = 2UL + + let data = [ (0UL, 0UL, 7) ] + let coo = CoordinateList(nrows, ncols, data) + + let f i j v = + match v with + | Some x -> Some x + | None -> Some(int (uint64 i + uint64 j)) + + let actual = cooMapi coo f + + let expected = + [ (0UL, 0UL, 7) + (0UL, 1UL, 1) + (1UL, 0UL, 1) + (1UL, 1UL, 2) ] + + Assert.Equal * uint64 * int>>(expected, actual.list) + +[] +let ``cooMapi zero-size matrix`` () = + let coo = CoordinateList(0UL, 0UL, []) + let f _i _j v = v |> Option.map (fun v -> v * 2) + let actual = cooMapi coo f + let expected = CoordinateList(0UL, 0UL, []) + Assert.Equal(expected, actual) + +// === cooMap2i tests === + +[] +let ``cooMap2i position-dependent addition`` () = + let nrows = 4UL + let ncols = 4UL + + let d1 = [ (0UL, 0UL, 1); (2UL, 2UL, 3) ] + let d2 = [ (0UL, 0UL, 10); (2UL, 2UL, 30) ] + + let f i j x y = + match x, y with + | Some a, Some b -> Some(a + b + (int (uint64 i))) + | Some a, None -> Some a + | None, Some b -> Some b + | _ -> None + + let expected = + CoordinateList( + nrows, + ncols, + [ (0UL, 0UL, 11) + (2UL, 2UL, 35) ] + ) + + let c1 = CoordinateList(nrows, ncols, d1) + let c2 = CoordinateList(nrows, ncols, d2) + let actual = cooMap2i c1 c2 f + + Assert.Equal(expected, actual) + +[] +let ``cooMap2i mismatched positions with index`` () = + let nrows = 4UL + let ncols = 4UL + + let d1 = [ (0UL, 0UL, 1); (2UL, 2UL, 3) ] + let d2 = [ (1UL, 1UL, 10); (3UL, 3UL, 30) ] + + let f i j x y = + match x, y with + | Some a, Some b -> Some(a + b) + | Some a, None -> Some(a + (int (uint64 j))) + | None, Some b -> Some(b + (int (uint64 i))) + | _ -> None + + let expected = + CoordinateList( + nrows, + ncols, + [ (0UL, 0UL, 1) + (1UL, 1UL, 11) + (2UL, 2UL, 5) + (3UL, 3UL, 33) ] + ) + + let c1 = CoordinateList(nrows, ncols, d1) + let c2 = CoordinateList(nrows, ncols, d2) + let actual = cooMap2i c1 c2 f + + Assert.Equal(expected, actual) + +[] +let ``cooMap2i filters None results`` () = + let nrows = 4UL + let ncols = 4UL + + let d1 = [ (0UL, 0UL, 1); (1UL, 1UL, 2) ] + let d2 = [ (0UL, 0UL, 2); (2UL, 2UL, 30) ] + + let f i j x y = + match x, y with + | Some a, Some b when a + b > 5 -> None + | Some a, Some b -> Some(a + b) + | _ -> None + + let expected = + CoordinateList( + nrows, + ncols, + [ (0UL, 0UL, 3) ] + ) + + let c1 = CoordinateList(nrows, ncols, d1) + let c2 = CoordinateList(nrows, ncols, d2) + let actual = cooMap2i c1 c2 f + + Assert.Equal(expected, actual) + +[] +let ``cooMap2i empty inputs`` () = + let c1 = CoordinateList(4UL, 4UL, []) + let c2 = CoordinateList(4UL, 4UL, []) + let f _i _j x y = None + let actual = cooMap2i c1 c2 f + let expected = CoordinateList(4UL, 4UL, []) + Assert.Equal(expected, actual) + +// === mxmcoo tests === + +[] +let ``Sparse mxmcoo`` () = + let m1 = + let d = + [ 0UL, 0UL, 1 + 1UL, 1UL, 2 + 2UL, 2UL, 3 ] + + CoordinateList(3UL, 3UL, d) + + let m2 = + let d = + [ 0UL, 0UL, 3 + 1UL, 1UL, 2 + 2UL, 2UL, 1 ] + + CoordinateList(3UL, 3UL, d) + + let expected = + let d = + [ 0UL, 0UL, 3 + 1UL, 1UL, 4 + 2UL, 2UL, 3 ] + + CoordinateList(3UL, 3UL, d) + + match COO.mxmcoo op_add op_mult m1 m2 with + | Ok actual -> + Assert.Equal(expected.nrows, actual.nrows) + Assert.Equal(expected.ncols, actual.ncols) + Assert.Equal>(expected.list, actual.list) + | Error e -> failwith (e.ToString()) + +[] +let ``Shrinking mxmcoo`` () = + let m1 = + let d = + [ 0UL, 0UL, 1 + 0UL, 2UL, 2 + 1UL, 1UL, 3 ] + + CoordinateList(2UL, 3UL, d) + + let m2 = + let d = + [ 0UL, 1UL, 4 + 1UL, 0UL, 5 + 2UL, 0UL, 6 ] + + CoordinateList(3UL, 2UL, d) + + let expected = + let d = + [ 0UL, 0UL, 12 + 0UL, 1UL, 4 + 1UL, 0UL, 15 ] + + CoordinateList(2UL, 2UL, d) + + match COO.mxmcoo op_add op_mult m1 m2 with + | Ok actual -> + Assert.Equal(expected.nrows, actual.nrows) + Assert.Equal(expected.ncols, actual.ncols) + Assert.Equal>(expected.list, actual.list) + | Error e -> failwith (e.ToString()) + + +[] +let ``mxmcoo with non-absorbing op_mult`` () = + 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) + | Some a, _ | _, Some a -> Some a + | _ -> None + + let m1 = + let d = + [ 0UL, 0UL, 1 + 0UL, 1UL, 2 ] + + CoordinateList(1UL, 2UL, d) + + let m2 = + let d = + [ 0UL, 0UL, 3 ] + + CoordinateList(2UL, 1UL, d) + + match COO.mxmcoo op_add op_mult m1 m2 with + | Ok actual -> + Assert.Equal(1UL, actual.nrows) + Assert.Equal(1UL, actual.ncols) + Assert.Equal(1, actual.list.Length) + Assert.Equal(Some 5, actual.list |> List.tryHead |> Option.map (fun (_, _, v) -> v)) + | Error e -> failwith (e.ToString()) diff --git a/QuadTree/COO.fs b/QuadTree/COO.fs new file mode 100644 index 0000000..26f792c --- /dev/null +++ b/QuadTree/COO.fs @@ -0,0 +1,284 @@ +module COO + +open Common +open Matrix + +let private range (count: uint64) = + if count = 0UL then [] else [ 0UL .. count - 1UL ] + +let cooGet + (coo: CoordinateList<'a>, rowindex: uint64, colindex: uint64) + : Result, Error> = + if uint64 rowindex >= uint64 coo.nrows || uint64 colindex >= uint64 coo.ncols then + Error Error.InvalidElementIndex + else + match coo.list |> List.tryFind (fun (i, j, _) -> i = rowindex && j = colindex) with + | Some(_, _, value) -> Ok(Some value) + | None -> Ok None + +let cooUpdate + (coo: CoordinateList<'a>, rowindex: uint64, colindex: uint64, value: 'a) + : Result, Error> = + if uint64 rowindex >= uint64 coo.nrows || uint64 colindex >= uint64 coo.ncols then + Error Error.InvalidElementIndex + else + let mutable acc = [] + let mutable rest = coo.list + let mutable inserted = false + + while rest <> [] && not inserted do + let (i, j, v) = rest.Head + + if i = rowindex && j = colindex then + acc <- (rowindex, colindex, value) :: acc + rest <- rest.Tail + inserted <- true + elif rowindex < i || (rowindex = i && colindex < j) then + acc <- (rowindex, colindex, value) :: acc + inserted <- true + else + acc <- (i, j, v) :: acc + rest <- rest.Tail + + if not inserted then + acc <- (rowindex, colindex, value) :: acc + + while rest <> [] do + let entry = rest.Head + acc <- entry :: acc + rest <- rest.Tail + + Ok(CoordinateList(coo.nrows, coo.ncols, List.rev acc)) + + +let cooMap (coo: CoordinateList<'a>) f = + let updatedList = coo.list |> List.map (fun (i, j, v) -> (i, j, f (Some v))) + + let result = + match f None with + | None -> + updatedList + |> List.choose (fun (i, j, v) -> v |> Option.map (fun v -> (i, j, v))) + | Some fnone -> + let lookup = + updatedList + |> List.map (fun (i, j, v) -> ((i, j), v)) + |> Map.ofList + + [ for i in range (uint64 coo.nrows) do + let ri = i * 1UL + + for j in range (uint64 coo.ncols) do + let cj = j * 1UL + + match Map.tryFind (ri, cj) lookup with + | Some(Some value) -> yield (ri, cj, value) + | Some None -> () + | None -> yield (ri, cj, fnone) ] + + CoordinateList(coo.nrows, coo.ncols, result) + +let cooMapi (coo: CoordinateList<'a>) f = + let lookup = + coo.list + |> List.map (fun (i, j, v) -> ((i, j), v)) + |> Map.ofList + + let result = + [ for i in range (uint64 coo.nrows) do + let ri = i * 1UL + + for j in range (uint64 coo.ncols) do + let cj = j * 1UL + + let res = f ri cj (Map.tryFind (ri, cj) lookup) + + match res with + | Some value -> yield (ri, cj, value) + | None -> () ] + + CoordinateList(coo.nrows, coo.ncols, result) + +let cooMap2 (coo1: CoordinateList<'a>) (coo2: CoordinateList<'b>) f = + let mutable acc = [] + let mutable l1 = coo1.list + let mutable l2 = coo2.list + + while l1 <> [] || l2 <> [] do + match l1, l2 with + | [], [] -> () + | (i1, j1, v1) :: t1, [] -> + let r = f (Some v1) None + acc <- (i1, j1, r) :: acc + l1 <- t1 + | [], (i2, j2, v2) :: t2 -> + let r = f None (Some v2) + acc <- (i2, j2, r) :: acc + l2 <- t2 + | (i1, j1, v1) :: t1, (i2, j2, v2) :: t2 -> + if i1 = i2 && j1 = j2 then + let r = f (Some v1) (Some v2) + acc <- (i1, j1, r) :: acc + l1 <- t1 + l2 <- t2 + elif (i1, j1) < (i2, j2) then + let r = f (Some v1) None + acc <- (i1, j1, r) :: acc + l1 <- t1 + else + let r = f None (Some v2) + acc <- (i2, j2, r) :: acc + l2 <- t2 + + let updatedList = List.rev acc + + let result = + match f None None with + | None -> + updatedList + |> List.choose (fun (i, j, v) -> v |> Option.map (fun v -> (i, j, v))) + | Some fnone -> + let lookup = + updatedList + |> List.map (fun (i, j, v) -> ((i, j), v)) + |> Map.ofList + + [ for i in range (uint64 coo1.nrows) do + let ri = i * 1UL + + for j in range (uint64 coo1.ncols) do + let cj = j * 1UL + + match Map.tryFind (ri, cj) lookup with + | Some(Some value) -> yield (ri, cj, value) + | Some None -> () + | None -> yield (ri, cj, fnone) ] + + CoordinateList(coo1.nrows, coo1.ncols, result) + +let cooMap2i (coo1: CoordinateList<'a>) (coo2: CoordinateList<'b>) f = + let mutable acc = [] + let mutable l1 = coo1.list + let mutable l2 = coo2.list + + while l1 <> [] || l2 <> [] do + match l1, l2 with + | [], [] -> () + | (i1, j1, v1) :: t1, [] -> + let r = f i1 j1 (Some v1) None + acc <- (i1, j1, r) :: acc + l1 <- t1 + | [], (i2, j2, v2) :: t2 -> + let r = f i2 j2 None (Some v2) + acc <- (i2, j2, r) :: acc + l2 <- t2 + | (i1, j1, v1) :: t1, (i2, j2, v2) :: t2 -> + if i1 = i2 && j1 = j2 then + let r = f i1 j1 (Some v1) (Some v2) + acc <- (i1, j1, r) :: acc + l1 <- t1 + l2 <- t2 + elif (i1, j1) < (i2, j2) then + let r = f i1 j1 (Some v1) None + acc <- (i1, j1, r) :: acc + l1 <- t1 + else + let r = f i2 j2 None (Some v2) + acc <- (i2, j2, r) :: acc + l2 <- t2 + + let result = + List.rev acc + |> List.choose (fun (i, j, v) -> v |> Option.map (fun v -> (i, j, v))) + + CoordinateList(coo1.nrows, coo1.ncols, result) + +let mxmcoo + (op_add: 'c option -> 'c option -> 'c option) + (op_mult: 'a option -> 'b option -> 'c option) + (m1: CoordinateList<'a>) + (m2: CoordinateList<'b>) + = + if uint64 m1.ncols <> uint64 m2.nrows then + Error Error.InconsistentSizeOfArguments + else + let firstA = m1.list |> List.tryHead |> Option.map (fun (_, _, v) -> v) + let firstB = m2.list |> List.tryHead |> Option.map (fun (_, _, v) -> v) + + let canOptimize = + let noneNone = op_mult None None = None + + let multSomeNone = + match firstA with + | Some v -> op_mult (Some v) None = None + | None -> noneNone + + let multNoneSome = + match firstB with + | Some v -> op_mult None (Some v) = None + | None -> noneNone + + let addNoneSome = + match firstA with + | Some v -> op_add (Some v) None = Some v + | None -> noneNone + + let addSomeNone = + match firstB with + | Some v -> op_add None (Some v) = Some v + | None -> noneNone + + noneNone && multSomeNone && multNoneSome && addNoneSome && addSomeNone + + if canOptimize then + let m1ByRow = m1.list |> List.groupBy (fun (i, _, _) -> i) |> Map.ofList + let m2ByRow = m2.list |> List.groupBy (fun (k, _, _) -> k) |> Map.ofList + + let result = + [ for KeyValue(i, m1Entries) in m1ByRow do + for (_, k, v1) in m1Entries do + let kAsRow = uint64 k * 1UL + + match m2ByRow |> Map.tryFind kAsRow with + | Some m2Entries -> + for (_, j, v2) in m2Entries do + match op_mult (Some v1) (Some v2) with + | Some product -> yield (i, j, product) + | None -> () + | None -> () ] + + let grouped = + result + |> List.groupBy (fun (i, j, _) -> (i, j)) + |> List.map (fun ((i, j), entries) -> + let sum = entries |> List.map (fun (_, _, v) -> Some v) |> List.reduce op_add + (i, j, sum)) + |> List.choose (fun (i, j, v) -> v |> Option.map (fun v -> (i, j, v))) + |> List.sortBy (fun (i, j, _) -> (i, j)) + + CoordinateList(m1.nrows, m2.ncols, grouped) |> Ok + else + let m1Map = m1.list |> List.map (fun (i, j, v) -> ((i, j), v)) |> Map.ofList + let m2Map = m2.list |> List.map (fun (i, j, v) -> ((i, j), v)) |> Map.ofList + let kCount = uint64 m1.ncols + + let result = + [ for i in range (uint64 m1.nrows) do + let ri = i * 1UL + + for j in range (uint64 m2.ncols) do + let cj = j * 1UL + + let products = + [ for k in range kCount do + let a = m1Map |> Map.tryFind (ri, k * 1UL) + let b = m2Map |> Map.tryFind (k * 1UL, cj) + yield op_mult a b ] + + let sum = products |> List.fold (fun acc p -> op_add acc p) None + + match sum with + | Some value -> yield (ri, cj, value) + | None -> () ] + + CoordinateList(m1.nrows, m2.ncols, result) |> Ok From 41194a49b0bc1aa1bac1f7794f23346f2f8c9a0e Mon Sep 17 00:00:00 2001 From: Narysev Date: Thu, 17 Sep 2026 15:32:51 +0300 Subject: [PATCH 4/5] Add public Matrix get/set/map, get/set/map tests; drop Matrix.filter/exists/forall tests --- QuadTree.Benchmark/Main.fs | 5 +- QuadTree.Benchmark/QuadTree.Benchmark.fsproj | 2 + QuadTree.Benchmark/Utils.fs | 14 +- QuadTree.Tests/QuadTree.Tests.fsproj | 1 + QuadTree.Tests/Tests.LinearAlgebra.fs | 1 + QuadTree.Tests/Tests.Matrix.fs | 502 +++---------------- QuadTree/Matrix.fs | 128 ++++- QuadTree/QuadTree.fsproj | 1 + 8 files changed, 198 insertions(+), 456 deletions(-) diff --git a/QuadTree.Benchmark/Main.fs b/QuadTree.Benchmark/Main.fs index 3fb2bf4..bcb51dc 100644 --- a/QuadTree.Benchmark/Main.fs +++ b/QuadTree.Benchmark/Main.fs @@ -9,7 +9,10 @@ let main argv = typeof typeof typeof - typeof |] + typeof + typeof + typeof + typeof |] benchmarks.Run argv |> ignore 0 diff --git a/QuadTree.Benchmark/QuadTree.Benchmark.fsproj b/QuadTree.Benchmark/QuadTree.Benchmark.fsproj index ba98b9d..b65f472 100644 --- a/QuadTree.Benchmark/QuadTree.Benchmark.fsproj +++ b/QuadTree.Benchmark/QuadTree.Benchmark.fsproj @@ -14,6 +14,8 @@ + + diff --git a/QuadTree.Benchmark/Utils.fs b/QuadTree.Benchmark/Utils.fs index 5ec8793..b71d03d 100644 --- a/QuadTree.Benchmark/Utils.fs +++ b/QuadTree.Benchmark/Utils.fs @@ -8,7 +8,7 @@ type MyConfig() = let DIR_WITH_MATRICES = "../../../../../../../data/" -let readMtx path directed = +let readMtxRaw path directed = let getCooList (linewords: seq) = linewords |> Seq.map (fun x -> @@ -23,7 +23,7 @@ let readMtx path directed = let lines = File.ReadLines(path) let removedComments = lines |> Seq.skipWhile (fun s -> s.[0] = '%') - let linewords = removedComments |> Seq.map (fun s -> s.Split " ") + let linewords = removedComments |> Seq.map (fun s -> s.Split [|' '|]) let first = Seq.head linewords let nrows, ncols, nnz = uint64 first.[0], uint64 first.[1], int first.[2] @@ -33,7 +33,11 @@ let readMtx path directed = let lst = getCooList tl if (directed && nnz <> lst.Length) || ((not directed) && nnz * 2 <> lst.Length) then - failwithf "Incorrect matrix reading. Path: %A expected nnz: %A actual nnz: %A" path (nnz * 2) lst.Length + failwithf "Incorrect matrix reading. Path: %A expected nnz: %A actual nnz: %A" path (if directed then nnz else nnz * 2) lst.Length + + let coo = Matrix.CoordinateList(nrows * 1UL, ncols * 1UL, lst) + let qt = Matrix.fromCoordinateList coo + (coo, qt) - Matrix.CoordinateList(nrows * 1UL, nrows * 1UL, lst) - |> Matrix.fromCoordinateList +let readMtx path directed = + readMtxRaw path directed |> snd diff --git a/QuadTree.Tests/QuadTree.Tests.fsproj b/QuadTree.Tests/QuadTree.Tests.fsproj index 4de3d64..dd6e038 100644 --- a/QuadTree.Tests/QuadTree.Tests.fsproj +++ b/QuadTree.Tests/QuadTree.Tests.fsproj @@ -8,6 +8,7 @@ + diff --git a/QuadTree.Tests/Tests.LinearAlgebra.fs b/QuadTree.Tests/Tests.LinearAlgebra.fs index 3bde7b3..23540a8 100644 --- a/QuadTree.Tests/Tests.LinearAlgebra.fs +++ b/QuadTree.Tests/Tests.LinearAlgebra.fs @@ -4,6 +4,7 @@ open System open Xunit open Matrix +open COO open Vector open Common diff --git a/QuadTree.Tests/Tests.Matrix.fs b/QuadTree.Tests/Tests.Matrix.fs index e0192a8..53210b3 100644 --- a/QuadTree.Tests/Tests.Matrix.fs +++ b/QuadTree.Tests/Tests.Matrix.fs @@ -1,9 +1,10 @@ -module Matrix.Tests +module Matrix.Tests open System open Xunit open Matrix +open COO open Common let printMatrix (matrix: SparseMatrix<_>) = @@ -644,493 +645,108 @@ let ``Fold sum`` () = Assert.Equal(expected, actual) [] -let ``Matrix.filter all pass, none changed`` () = +let ``matrix get existing value`` () = let m = - Matrix.fromCoordinateList ( - CoordinateList( - 4UL, - 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ] - ) - ) + fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 7); (1UL, 2UL, 9) ])) - let actual = Matrix.filter m (fun x -> x > 0) - Assert.Equal(m, actual) + Assert.Equal(Ok(Some 7), get m 0UL 0UL) + Assert.Equal(Ok(Some 9), get m 1UL 2UL) [] -let ``Matrix.filter none pass, all reset to zero`` () = +let ``matrix get missing value`` () = let m = - Matrix.fromCoordinateList ( - CoordinateList( - 4UL, - 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ] - ) - ) - - let expected = - Matrix.fromCoordinateList (CoordinateList(4UL, 4UL, [])) + fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 7) ])) - let actual = Matrix.filter m (fun x -> x < 0) - - Assert.Equal(expected, actual) + Assert.Equal(Ok None, get m 1UL 1UL) [] -let ``Matrix.filter length is not a power of 2, all reset to zero`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 3UL, - 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) - (2UL, 0UL, 7) - (2UL, 1UL, 8) - (2UL, 2UL, 9) ] - ) - ) - - let expected = - Matrix.fromCoordinateList (CoordinateList(3UL, 3UL, [])) - - let actual = Matrix.filter m (fun x -> x < 0) - - Assert.Equal(expected, actual) +let ``matrix get out of bounds`` () = + let m = fromCoordinateList(CoordinateList(4UL, 4UL, [])) + Assert.Equal(Error Error.InvalidElementIndex, get m 5UL 5UL) [] -let ``Matrix.filter some pass, odd set to zero`` () = +let ``matrix set replaces existing`` () = let m = - Matrix.fromCoordinateList ( - CoordinateList( - 4UL, - 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (0UL, 3UL, 4) - (1UL, 0UL, 5) - (1UL, 1UL, 6) - (1UL, 2UL, 7) - (1UL, 3UL, 8) - (2UL, 0UL, 9) - (2UL, 1UL, 10) - (2UL, 2UL, 11) - (2UL, 3UL, 12) - (3UL, 0UL, 13) - (3UL, 1UL, 14) - (3UL, 2UL, 15) - (3UL, 3UL, 16) ] - ) - ) + fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 7) ])) - let expected = - Matrix.fromCoordinateList ( - CoordinateList( - 4UL, - 4UL, - [ (0UL, 1UL, 2) - (0UL, 3UL, 4) - (1UL, 1UL, 6) - (1UL, 3UL, 8) - (2UL, 1UL, 10) - (2UL, 3UL, 12) - (3UL, 1UL, 14) - (3UL, 3UL, 16) ] - ) - ) + let actual = set m 0UL 0UL 99 |> Result.defaultValue m - let actual = Matrix.filter m (fun x -> x % 2 = 0) - - Assert.Equal(expected, actual) + Assert.Equal(Ok(Some 99), get actual 0UL 0UL) + Assert.Equal(Ok(Some 7), get m 0UL 0UL) [] -let ``Matrix.filter length is not a power of 2, not all reset to zero`` () = +let ``matrix set inserts new`` () = let m = - Matrix.fromCoordinateList ( - CoordinateList( - 3UL, - 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) - (2UL, 0UL, 7) - (2UL, 1UL, 8) - (2UL, 2UL, 9) ] - ) - ) + fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 7) ])) - let expected = - Matrix.fromCoordinateList ( - CoordinateList( - 3UL, - 3UL, - [ (0UL, 1UL, 2) - (1UL, 0UL, 4) - (1UL, 2UL, 6) - (2UL, 1UL, 8) ] - ) - ) + let actual = set m 2UL 2UL 42 |> Result.defaultValue m - let actual = Matrix.filter m (fun x -> x % 2 = 0) - - Assert.Equal(expected, actual) + Assert.Equal(Ok(Some 42), get actual 2UL 2UL) [] -let ``Matrix.filter none pass, length is not a power of 2, none changed`` () = - let m = Matrix.fromCoordinateList (CoordinateList(3UL, 3UL, [])) - let actual = Matrix.filter m (fun x -> x > 0) - Assert.Equal(m, actual) +let ``matrix set out of bounds`` () = + let m = fromCoordinateList(CoordinateList(4UL, 4UL, [])) + Assert.Equal(Error Error.InvalidElementIndex, set m 5UL 5UL 99) [] -let ``Matrix.filter none pass, length is a power of 2, none changed`` () = - let m = Matrix.fromCoordinateList (CoordinateList(4UL, 4UL, [])) - let actual = Matrix.filter m (fun x -> x > 0) - Assert.Equal(m, actual) - -[] -let ``Matrix.filter single element, passes`` () = - let m = - Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) - - let expected = - Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) - - let actual = Matrix.filter m (fun x -> x > 0) +let ``matrix set then get roundtrip`` () = + let m0 = empty 4UL 4UL + let m1 = set m0 0UL 0UL 1 |> Result.defaultValue m0 + let m2 = set m1 1UL 2UL 2 |> Result.defaultValue m1 + let m3 = set m2 3UL 3UL 3 |> Result.defaultValue m2 - Assert.Equal(expected, actual) + Assert.Equal(Ok(Some 1), get m3 0UL 0UL) + Assert.Equal(Ok(Some 2), get m3 1UL 2UL) + Assert.Equal(Ok(Some 3), get m3 3UL 3UL) + Assert.Equal(Ok(None), get m3 2UL 1UL) [] -let ``Matrix.filter single element, fails`` () = +let ``matrix map doubles values`` () = let m = - Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) - - let expected = - Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [])) + fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 3); (1UL, 2UL, 5) ])) - let actual = Matrix.filter m (fun x -> x < 0) + let result = map m (Option.map (fun v -> v * 2)) - Assert.Equal(expected, actual) + Assert.Equal(Ok(Some 6), get result 0UL 0UL) + Assert.Equal(Ok(Some 10), get result 1UL 2UL) + Assert.Equal(Ok(None), get result 0UL 1UL) [] -let ``Matrix.exists the first element fits, length is a power of 2`` () = +let ``matrix map filters Some to None`` () = let m = - Matrix.fromCoordinateList ( - CoordinateList( - 4UL, - 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ] - ) - ) + fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 3); (1UL, 2UL, 5) ])) - Assert.True(Matrix.exists m (fun x -> x = 1)) - Assert.False(Matrix.exists m (fun x -> x = 10)) + let result = map m (fun v -> match v with Some x when x > 4 -> Some x | _ -> None) -[] -let ``Matrix.exists the first element fits, length is not a power of 2`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 3UL, - 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ] - ) - ) - - Assert.True(Matrix.exists m (fun x -> x = 1)) + Assert.Equal(Ok(None), get result 0UL 0UL) + Assert.Equal(Ok(Some 5), get result 1UL 2UL) [] -let ``Matrix.exists no element fits, length is a power of 2`` () = +let ``matrix map fills None with values`` () = let m = - Matrix.fromCoordinateList ( - CoordinateList( - 4UL, - 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ] - ) - ) + fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 3) ])) - Assert.False(Matrix.exists m (fun x -> x = 10)) + let result = map m (fun v -> Some(match v with Some x -> x | None -> 0)) -[] -let ``Matrix.exists no element fits, length is not a power of 2`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 3UL, - 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ] - ) - ) - - Assert.False(Matrix.exists m (fun x -> x = 10)) + Assert.Equal(Ok(Some 3), get result 0UL 0UL) + Assert.Equal(Ok(Some 0), get result 1UL 1UL) + Assert.Equal(Ok(Some 0), get result 3UL 3UL) [] -let ``Matrix.exists empty matrix`` () = - let m = Matrix.fromCoordinateList (CoordinateList(3UL, 3UL, [])) - Assert.False(Matrix.exists m (fun x -> x = 1)) +let ``matrix map on empty matrix`` () = + let m = empty 4UL 4UL -[] -let ``Matrix.exists single element, fits`` () = - let m = - Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) - - Assert.True(Matrix.exists m (fun x -> x = 1)) - -[] -let ``Matrix.exists single element, does not fit`` () = - let m = - Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) + let result = map m (Option.map (fun v -> v + 1)) - Assert.False(Matrix.exists m (fun x -> x = 10)) + Assert.Equal(Ok(None), get result 0UL 0UL) [] -let ``Matrix.exists all elements fit,length is a power of 2`` () = +let ``matrix map nvals updated`` () = let m = - Matrix.fromCoordinateList ( - CoordinateList( - 4UL, - 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ] - ) - ) + fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 3); (1UL, 2UL, 5) ])) - Assert.True(Matrix.exists m (fun x -> x > 0)) + Assert.Equal(2UL, uint64 m.nvals) -[] -let ``Matrix.exists all elements fit,length is not a power of 2`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 3UL, - 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ] - ) - ) - - Assert.True(Matrix.exists m (fun x -> x > 0)) - -[] -let ``Matrix.exists some elements fit, length is a power of 2`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 4UL, - 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ] - ) - ) - - Assert.True(Matrix.exists m (fun x -> x = 2 || x = 10)) - -[] -let ``Matrix.exists some elements fit, length is not a power of 2`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 3UL, - 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ] - ) - ) - - Assert.True(Matrix.exists m (fun x -> x = 2 || x = 10)) - -[] -let ``Matrix.forall the first element fits, length is a power of 2`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 4UL, - 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ] - ) - ) - - Assert.True(Matrix.forall m (fun x -> x > 0)) - -[] -let ``Matrix.forall the first element fits, length is not a power of 2`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 3UL, - 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ] - ) - ) - - Assert.True(Matrix.forall m (fun x -> x > 0)) - -[] -let ``Matrix.forall no element fits, length is a power of 2`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 4UL, - 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ] - ) - ) - - Assert.False(Matrix.forall m (fun x -> x > 10)) - -[] -let ``Matrix.forall no element fits, length is not a power of 2`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 3UL, - 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ] - ) - ) - - Assert.False(Matrix.forall m (fun x -> x > 10)) - -[] -let ``Matrix.forall all elements fit,length is a power of 2`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 4UL, - 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ] - ) - ) - - Assert.True(Matrix.forall m (fun x -> x > 0)) - -[] -let ``Matrix.forall all elements fit,length is not a power of 2`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 3UL, - 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ] - ) - ) - - Assert.True(Matrix.forall m (fun x -> x > 0)) - -[] -let ``Matrix.forall some elements fit, length is a power of 2`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 4UL, - 4UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (1UL, 0UL, 3) - (1UL, 1UL, 4) ] - ) - ) - - Assert.False(Matrix.forall m (fun x -> x = 2 || x = 10)) - -[] -let ``Matrix.forall some elements fit, length is not a power of 2`` () = - let m = - Matrix.fromCoordinateList ( - CoordinateList( - 3UL, - 3UL, - [ (0UL, 0UL, 1) - (0UL, 1UL, 2) - (0UL, 2UL, 3) - (1UL, 0UL, 4) - (1UL, 1UL, 5) - (1UL, 2UL, 6) ] - ) - ) - - Assert.False(Matrix.forall m (fun x -> x = 2 || x = 10)) - -[] -let ``Matrix.forall empty matrix`` () = - let m = Matrix.fromCoordinateList (CoordinateList(3UL, 3UL, [])) - Assert.True(Matrix.forall m (fun x -> x = 1)) - -[] -let ``Matrix.forall single element, fits`` () = - let m = - Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) - - Assert.True(Matrix.forall m (fun x -> x = 1)) - -[] -let ``Matrix.forall single element, does not fit`` () = - let m = - Matrix.fromCoordinateList (CoordinateList(1UL, 1UL, [ (0UL, 0UL, 1) ])) + let result = map m (fun _ -> None) - Assert.False(Matrix.forall m (fun x -> x = 10)) + Assert.Equal(0UL, uint64 result.nvals) diff --git a/QuadTree/Matrix.fs b/QuadTree/Matrix.fs index e78adf7..a75df31 100644 --- a/QuadTree/Matrix.fs +++ b/QuadTree/Matrix.fs @@ -41,6 +41,7 @@ type SparseMatrix<'value> = type Error = | InconsistentStructureOfStorages | InconsistentSizeOfArguments + | InvalidElementIndex let mkNode x1 x2 x3 x4 = @@ -54,6 +55,12 @@ type rowindex [] type colindex +let getQuadrantCoords (pr, pc) halfSize = + (pr, pc), // NORTH WEST + (pr, pc + halfSize * 1UL), // NORTH EAST + (pr + halfSize * 1UL, pc), // SOUTH WEST + (pr + halfSize * 1UL, pc + halfSize * 1UL) // SOUTH EAST + type COOEntry<'value> = uint64 * uint64 * 'value [] @@ -67,18 +74,11 @@ type CoordinateList<'value> = ncols = _ncols list = _list } -let private getQuadrantCoords (pr, pc) halfSize = - (pr, pc), // NORTH WEST - (pr, pc + halfSize * 1UL), // NORTH EAST - (pr + halfSize * 1UL, pc), // SOUTH WEST - (pr + halfSize * 1UL, pc + halfSize * 1UL) // SOUTH EAST - let fromCoordinateList (coo: CoordinateList<'a>) = let nvals = (uint64 <| List.length coo.list) * 1UL let nrows = coo.nrows let ncols = coo.ncols - // the resulting matrix is always square let storageSize = getNearestUpperPowerOfTwo (max (uint64 nrows) (uint64 ncols)) let isEntryInQuadrant (pr, pc) size (entry: COOEntry<'a>) = @@ -140,6 +140,120 @@ let toCoordinateList (matrix: SparseMatrix<'a>) = let empty nrows ncols = fromCoordinateList (CoordinateList(nrows, ncols, [])) +let get (matrix: SparseMatrix<'a>) (row: uint64) (col: uint64) : Result, Error> = + if uint64 row >= uint64 matrix.nrows || uint64 col >= uint64 matrix.ncols then + Error Error.InvalidElementIndex + else + let rec inner tree (pr: uint64) (pc: uint64) (size: uint64) = + match tree with + | Leaf Dummy -> None + | Leaf(UserValue v) -> v + | Node(nw, ne, sw, se) -> + let halfSize = size / 2UL + let midR = pr + halfSize * 1UL + let midC = pc + halfSize * 1UL + + if uint64 row < uint64 midR then + if uint64 col < uint64 midC then + inner nw pr pc halfSize + else + inner ne pr midC halfSize + else if uint64 col < uint64 midC then + inner sw midR pc halfSize + else + inner se midR midC halfSize + + Ok(inner matrix.storage.data (0UL) (0UL) (uint64 matrix.storage.size)) + +let set + (matrix: SparseMatrix<'a>) + (row: uint64) + (col: uint64) + (value: 'a) + : Result, Error> = + if uint64 row >= uint64 matrix.nrows || uint64 col >= uint64 matrix.ncols then + Error Error.InvalidElementIndex + else + let rec inner tree (pr: uint64) (pc: uint64) (size: uint64) = + let halfSize = size / 2UL + + if size = 1UL then + match tree with + | Leaf(UserValue oldVal) -> + let newVal = Some value + + let delta = + match newVal, oldVal with + | Some _, None -> 1L + | None, Some _ -> -1L + | _ -> 0L + + Leaf(UserValue newVal), delta + | Leaf Dummy -> Leaf(UserValue(Some value)), 1L + | _ -> failwith "Unreachable" + else + let midR = pr + halfSize * 1UL + let midC = pc + halfSize * 1UL + + let (nw, ne, sw, se) = + match tree with + | Node(nw, ne, sw, se) -> nw, ne, sw, se + | Leaf v -> Leaf v, Leaf v, Leaf v, Leaf v + + let newChild, delta = + if uint64 row < uint64 midR then + if uint64 col < uint64 midC then + inner nw pr pc halfSize + else + inner ne pr midC halfSize + else if uint64 col < uint64 midC then + inner sw midR pc halfSize + else + inner se midR midC halfSize + + if uint64 row < uint64 midR then + if uint64 col < uint64 midC then + mkNode newChild ne sw se, delta + else + mkNode nw newChild sw se, delta + else if uint64 col < uint64 midC then + mkNode nw ne newChild se, delta + else + mkNode nw ne sw newChild, delta + + let storage, deltaNNZ = + inner matrix.storage.data (0UL) (0UL) (uint64 matrix.storage.size) + + let nvals = uint64 (int64 matrix.nvals + deltaNNZ) * 1UL + Ok(SparseMatrix(matrix.nrows, matrix.ncols, nvals, Storage(matrix.storage.size, storage))) + +let map (matrix: SparseMatrix<_>) f = + let rec inner (size: uint64) matrix = + match matrix with + | Leaf(Dummy) -> Leaf(Dummy), 0UL + | Leaf(UserValue(v)) -> + let res = f v + + let nnz = + match res with + | None -> 0UL + | _ -> (uint64 size) * (uint64 size) * 1UL + + Leaf(UserValue(res)), nnz + | Node(x1, x2, x3, x4) -> + let new_size = size / 2UL + + let t1, nvals1 = inner new_size x1 + let t2, nvals2 = inner new_size x2 + let t3, nvals3 = inner new_size x3 + let t4, nvals4 = inner new_size x4 + + mkNode t1 t2 t3 t4, nvals1 + nvals2 + nvals3 + nvals4 + + let storage, nvals = inner matrix.storage.size matrix.storage.data + + SparseMatrix(matrix.nrows, matrix.ncols, nvals, Storage(matrix.storage.size, storage)) + let map2 (matrix1: SparseMatrix<_>) (matrix2: SparseMatrix<_>) f = let rec inner (size: uint64) matrix1 matrix2 = let _do x1 x2 x3 x4 y1 y2 y3 y4 = diff --git a/QuadTree/QuadTree.fsproj b/QuadTree/QuadTree.fsproj index 438678c..618aded 100644 --- a/QuadTree/QuadTree.fsproj +++ b/QuadTree/QuadTree.fsproj @@ -9,6 +9,7 @@ + From 9e197d64cea274c7fef89bb5c76ed7c6e163afb6 Mon Sep 17 00:00:00 2001 From: Narysev Date: Sat, 19 Sep 2026 09:53:30 +0300 Subject: [PATCH 5/5] Implement COO operations, optimize sparse mxm path, add comprehensive test suite (188 tests) --- QuadTree.Tests/Tests.COO.fs | 310 ++++++++++++++++++++++---- QuadTree.Tests/Tests.Matrix.fs | 304 +++++++++++++++++++++++++- QuadTree/COO.fs | 308 ++++++++++++++++---------- QuadTree/Matrix.fs | 388 ++++++++++++++++++++------------- 4 files changed, 997 insertions(+), 313 deletions(-) diff --git a/QuadTree.Tests/Tests.COO.fs b/QuadTree.Tests/Tests.COO.fs index 2075f94..21a9b1a 100644 --- a/QuadTree.Tests/Tests.COO.fs +++ b/QuadTree.Tests/Tests.COO.fs @@ -161,7 +161,8 @@ let ``cooMap filters None results`` () = let coo = CoordinateList(nrows, ncols, data) let f v = - v |> Option.bind (fun v -> + v + |> Option.bind (fun v -> match v with | 1 -> None | _ -> Some(v * 10)) @@ -195,8 +196,11 @@ let ``cooMap fills missing cells (general form)`` () = Assert.Equal(nrows, actual.nrows) Assert.Equal(ncols, actual.ncols) Assert.Equal(9, actual.list.Length) - Assert.Equal(List.tryFind (fun (i, j, _) -> i = 2UL && j = 2UL) actual.list, - Some(2UL, 2UL, 5)) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 2UL && j = 2UL) actual.list, + Some(2UL, 2UL, 5) + ) [] let ``cooMap zero-size matrix`` () = @@ -248,7 +252,7 @@ let ``cooMap2 addition`` () = let actual = cooMap2 c1 c2 f - Assert.Equal(expected, actual) + Assert.Equal(Ok expected, actual) [] let ``cooMap2 with mismatched positions`` () = @@ -281,7 +285,7 @@ let ``cooMap2 with mismatched positions`` () = let actual = cooMap2 c1 c2 f - Assert.Equal(expected, actual) + Assert.Equal(Ok expected, actual) [] let ``cooMap2 dense filters None from existing entries`` () = @@ -325,7 +329,7 @@ let ``cooMap2 dense filters None from existing entries`` () = let actual = cooMap2 c1 c2 f - Assert.Equal(expected, actual) + Assert.Equal(Ok expected, actual) // === cooMapi tests === @@ -342,7 +346,8 @@ let ``cooMapi position-dependent values`` () = let coo = CoordinateList(nrows, ncols, data) - let f i j v = v |> Option.map (fun v -> v + (int (uint64 i))) + let f i j v = + v |> Option.map (fun v -> v + (int (uint64 i))) let expected = CoordinateList( @@ -369,15 +374,11 @@ let ``cooMapi filters None results`` () = let coo = CoordinateList(nrows, ncols, data) - let f _i _j v = v |> Option.bind (fun v -> if v > 2 then Some(v * 10) else None) + let f _i _j v = + v |> Option.bind (fun v -> if v > 2 then Some(v * 10) else None) let expected = - CoordinateList( - nrows, - ncols, - [ (0UL, 1UL, 50) - (1UL, 0UL, 30) ] - ) + CoordinateList(nrows, ncols, [ (0UL, 1UL, 50); (1UL, 0UL, 30) ]) let actual = cooMapi coo f @@ -406,8 +407,11 @@ let ``cooMapi fills missing cells (general form)`` () = Assert.Equal(nrows, actual.nrows) Assert.Equal(ncols, actual.ncols) Assert.Equal(9, actual.list.Length) - Assert.Equal(List.tryFind (fun (i, j, _) -> i = 2UL && j = 2UL) actual.list, - Some(2UL, 2UL, 5)) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 2UL && j = 2UL) actual.list, + Some(2UL, 2UL, 5) + ) [] let ``cooMapi position-dependent fill of missing cells`` () = @@ -458,18 +462,13 @@ let ``cooMap2i position-dependent addition`` () = | _ -> None let expected = - CoordinateList( - nrows, - ncols, - [ (0UL, 0UL, 11) - (2UL, 2UL, 35) ] - ) + CoordinateList(nrows, ncols, [ (0UL, 0UL, 11); (2UL, 2UL, 35) ]) let c1 = CoordinateList(nrows, ncols, d1) let c2 = CoordinateList(nrows, ncols, d2) let actual = cooMap2i c1 c2 f - Assert.Equal(expected, actual) + Assert.Equal(Ok expected, actual) [] let ``cooMap2i mismatched positions with index`` () = @@ -500,7 +499,7 @@ let ``cooMap2i mismatched positions with index`` () = let c2 = CoordinateList(nrows, ncols, d2) let actual = cooMap2i c1 c2 f - Assert.Equal(expected, actual) + Assert.Equal(Ok expected, actual) [] let ``cooMap2i filters None results`` () = @@ -516,18 +515,13 @@ let ``cooMap2i filters None results`` () = | Some a, Some b -> Some(a + b) | _ -> None - let expected = - CoordinateList( - nrows, - ncols, - [ (0UL, 0UL, 3) ] - ) + let expected = CoordinateList(nrows, ncols, [ (0UL, 0UL, 3) ]) let c1 = CoordinateList(nrows, ncols, d1) let c2 = CoordinateList(nrows, ncols, d2) let actual = cooMap2i c1 c2 f - Assert.Equal(expected, actual) + Assert.Equal(Ok expected, actual) [] let ``cooMap2i empty inputs`` () = @@ -536,7 +530,7 @@ let ``cooMap2i empty inputs`` () = let f _i _j x y = None let actual = cooMap2i c1 c2 f let expected = CoordinateList(4UL, 4UL, []) - Assert.Equal(expected, actual) + Assert.Equal(Ok expected, actual) // === mxmcoo tests === @@ -612,25 +606,24 @@ let ``mxmcoo with non-absorbing op_mult`` () = let op_add x y = match (x, y) with | Some(a), Some(b) -> Some(a + b) - | Some a, _ | _, Some a -> Some a + | Some a, _ + | _, Some a -> Some a | _ -> None let op_mult x y = match (x, y) with | Some(a), Some(b) -> Some(a * b) - | Some a, _ | _, Some a -> Some a + | Some a, _ + | _, Some a -> Some a | _ -> None let m1 = - let d = - [ 0UL, 0UL, 1 - 0UL, 1UL, 2 ] + let d = [ 0UL, 0UL, 1; 0UL, 1UL, 2 ] CoordinateList(1UL, 2UL, d) let m2 = - let d = - [ 0UL, 0UL, 3 ] + let d = [ 0UL, 0UL, 3 ] CoordinateList(2UL, 1UL, d) @@ -641,3 +634,242 @@ let ``mxmcoo with non-absorbing op_mult`` () = Assert.Equal(1, actual.list.Length) Assert.Equal(Some 5, actual.list |> List.tryHead |> Option.map (fun (_, _, v) -> v)) | Error e -> failwith (e.ToString()) + +// === cooMapValues / cooMapiValues tests === + +[] +let ``cooMapValues applies only to stored values`` () = + let coo = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 1); (1UL, 1UL, 2) ]) + + let actual = cooMapValues coo (fun v -> Some(v * 10)) + + Assert.Equal(2, actual.list.Length) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 0UL && j = 0UL) actual.list, + Some(0UL, 0UL, 10) + ) + +[] +let ``cooMapiValues applies indexed only to stored values`` () = + let coo = + CoordinateList(4UL, 4UL, [ (1UL, 2UL, 5) ]) + + let actual = + cooMapiValues coo (fun i j v -> Some(v + int (uint64 i) + int (uint64 j))) + + Assert.Equal(1, actual.list.Length) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 1UL && j = 2UL) actual.list, + Some(1UL, 2UL, 8) + ) + +// === cooMap2 variants tests === + +[] +let ``cooMap2Values applies only where both present`` () = + let c1 = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 1); (1UL, 1UL, 2) ]) + + let c2 = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 10) ]) + + match cooMap2Values c1 c2 (fun a b -> Some(a + b)) with + | Ok actual -> + Assert.Equal(1, actual.list.Length) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 0UL && j = 0UL) actual.list, + Some(0UL, 0UL, 11) + ) + | Error e -> failwithf "unexpected error %A" e + +[] +let ``cooMap2AllCells equals cooMap2`` () = + let c1 = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 1); (1UL, 1UL, 2) ]) + + let c2 = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 10) ]) + + let f a b = + match a, b with + | Some x, Some y -> Some(x + y) + | _ -> None + + Assert.Equal(cooMap2 c1 c2 f, cooMap2AllCells c1 c2 f) + +[] +let ``cooMap2AtLeastOne distinguishes both left right`` () = + let c1 = + CoordinateList(3UL, 3UL, [ (0UL, 0UL, 1); (1UL, 1UL, 2) ]) + + let c2 = + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 10); (2UL, 2UL, 30) ] + ) + + let f = + function + | AtLeastOne.Both(a, b) -> Some(a + b) + | AtLeastOne.Left a -> Some(a * 100) + | AtLeastOne.Right b -> Some(b * -1) + + match cooMap2AtLeastOne c1 c2 f with + | Ok actual -> + Assert.Equal(3, actual.list.Length) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 0UL && j = 0UL) actual.list, + Some(0UL, 0UL, 11) + ) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 1UL && j = 1UL) actual.list, + Some(1UL, 1UL, 200) + ) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 2UL && j = 2UL) actual.list, + Some(2UL, 2UL, -30) + ) + | Error e -> failwithf "unexpected error %A" e + +[] +let ``cooMap2LeftValues applies where left present`` () = + let c1 = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 1); (1UL, 1UL, 2) ]) + + let c2 = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 10) ]) + + match cooMap2LeftValues c1 c2 (fun a b -> Some(a + (defaultArg b 0))) with + | Ok actual -> + Assert.Equal(2, actual.list.Length) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 0UL && j = 0UL) actual.list, + Some(0UL, 0UL, 11) + ) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 1UL && j = 1UL) actual.list, + Some(1UL, 1UL, 2) + ) + | Error e -> failwithf "unexpected error %A" e + +[] +let ``cooMap2 sizes mismatch`` () = + let c1 = CoordinateList(4UL, 4UL, []) + let c2 = CoordinateList(2UL, 2UL, []) + let f a b = None + + Assert.Equal(Error Error.InconsistentSizeOfArguments, cooMap2 c1 c2 f) + +// === cooMap2i variants tests === + +[] +let ``cooMap2iValues applies indexed where both present`` () = + let c1 = + CoordinateList(4UL, 4UL, [ (1UL, 1UL, 2) ]) + + let c2 = + CoordinateList( + 4UL, + 4UL, + [ (1UL, 1UL, 10); (2UL, 2UL, 20) ] + ) + + let f i j a b = + Some(a + b + int (uint64 i) + int (uint64 j)) + + match cooMap2iValues c1 c2 f with + | Ok actual -> + Assert.Equal(1, actual.list.Length) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 1UL && j = 1UL) actual.list, + Some(1UL, 1UL, 14) + ) + | Error e -> failwithf "unexpected error %A" e + +[] +let ``cooMap2iAllCells equals cooMap2i`` () = + let c1 = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 1); (1UL, 1UL, 2) ]) + + let c2 = + CoordinateList(4UL, 4UL, [ (0UL, 0UL, 10) ]) + + let f i j a b = + match a, b with + | Some x, Some y -> Some(x + y + int (uint64 i)) + | _ -> None + + Assert.Equal(cooMap2i c1 c2 f, cooMap2iAllCells c1 c2 f) + +[] +let ``cooMap2iAtLeastOne passes indices and side`` () = + let c1 = + CoordinateList(2UL, 2UL, [ (0UL, 0UL, 1) ]) + + let c2 = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 10); (1UL, 1UL, 20) ] + ) + + let f i j = + function + | AtLeastOne.Both(a, b) -> Some(a + b + int (uint64 i) + int (uint64 j)) + | AtLeastOne.Left a -> Some(a) + | AtLeastOne.Right b -> Some(b + int (uint64 i) * 100 + int (uint64 j)) + + match cooMap2iAtLeastOne c1 c2 f with + | Ok actual -> + Assert.Equal(2, actual.list.Length) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 0UL && j = 0UL) actual.list, + Some(0UL, 0UL, 11) + ) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 1UL && j = 1UL) actual.list, + Some(1UL, 1UL, 121) + ) + | Error e -> failwithf "unexpected error %A" e + +[] +let ``cooMap2iLeftValues applies indexed where left present`` () = + let c1 = + CoordinateList(2UL, 2UL, [ (1UL, 1UL, 2) ]) + + let c2 = + CoordinateList(2UL, 2UL, [ (1UL, 1UL, 10) ]) + + let f i j a b = + Some(a + (defaultArg b 0) + int (uint64 i) * 10 + int (uint64 j)) + + match cooMap2iLeftValues c1 c2 f with + | Ok actual -> + Assert.Equal(1, actual.list.Length) + + Assert.Equal( + List.tryFind (fun (i, j, _) -> i = 1UL && j = 1UL) actual.list, + Some(1UL, 1UL, 23) + ) + | Error e -> failwithf "unexpected error %A" e + +[] +let ``cooMap2i sizes mismatch`` () = + let c1 = CoordinateList(4UL, 4UL, []) + let c2 = CoordinateList(2UL, 2UL, []) + let f _i _j a b = None + + Assert.Equal(Error Error.InconsistentSizeOfArguments, cooMap2i c1 c2 f) diff --git a/QuadTree.Tests/Tests.Matrix.fs b/QuadTree.Tests/Tests.Matrix.fs index 53210b3..9cef022 100644 --- a/QuadTree.Tests/Tests.Matrix.fs +++ b/QuadTree.Tests/Tests.Matrix.fs @@ -647,7 +647,13 @@ let ``Fold sum`` () = [] let ``matrix get existing value`` () = let m = - fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 7); (1UL, 2UL, 9) ])) + fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 7); (1UL, 2UL, 9) ] + ) + ) Assert.Equal(Ok(Some 7), get m 0UL 0UL) Assert.Equal(Ok(Some 9), get m 1UL 2UL) @@ -655,19 +661,19 @@ let ``matrix get existing value`` () = [] let ``matrix get missing value`` () = let m = - fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 7) ])) + fromCoordinateList (CoordinateList(4UL, 4UL, [ (0UL, 0UL, 7) ])) Assert.Equal(Ok None, get m 1UL 1UL) [] let ``matrix get out of bounds`` () = - let m = fromCoordinateList(CoordinateList(4UL, 4UL, [])) + let m = fromCoordinateList (CoordinateList(4UL, 4UL, [])) Assert.Equal(Error Error.InvalidElementIndex, get m 5UL 5UL) [] let ``matrix set replaces existing`` () = let m = - fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 7) ])) + fromCoordinateList (CoordinateList(4UL, 4UL, [ (0UL, 0UL, 7) ])) let actual = set m 0UL 0UL 99 |> Result.defaultValue m @@ -677,7 +683,7 @@ let ``matrix set replaces existing`` () = [] let ``matrix set inserts new`` () = let m = - fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 7) ])) + fromCoordinateList (CoordinateList(4UL, 4UL, [ (0UL, 0UL, 7) ])) let actual = set m 2UL 2UL 42 |> Result.defaultValue m @@ -685,7 +691,7 @@ let ``matrix set inserts new`` () = [] let ``matrix set out of bounds`` () = - let m = fromCoordinateList(CoordinateList(4UL, 4UL, [])) + let m = fromCoordinateList (CoordinateList(4UL, 4UL, [])) Assert.Equal(Error Error.InvalidElementIndex, set m 5UL 5UL 99) [] @@ -703,7 +709,13 @@ let ``matrix set then get roundtrip`` () = [] let ``matrix map doubles values`` () = let m = - fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 3); (1UL, 2UL, 5) ])) + fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 3); (1UL, 2UL, 5) ] + ) + ) let result = map m (Option.map (fun v -> v * 2)) @@ -714,9 +726,19 @@ let ``matrix map doubles values`` () = [] let ``matrix map filters Some to None`` () = let m = - fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 3); (1UL, 2UL, 5) ])) + fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 3); (1UL, 2UL, 5) ] + ) + ) - let result = map m (fun v -> match v with Some x when x > 4 -> Some x | _ -> None) + let result = + map m (fun v -> + match v with + | Some x when x > 4 -> Some x + | _ -> None) Assert.Equal(Ok(None), get result 0UL 0UL) Assert.Equal(Ok(Some 5), get result 1UL 2UL) @@ -724,9 +746,15 @@ let ``matrix map filters Some to None`` () = [] let ``matrix map fills None with values`` () = let m = - fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 3) ])) + fromCoordinateList (CoordinateList(4UL, 4UL, [ (0UL, 0UL, 3) ])) - let result = map m (fun v -> Some(match v with Some x -> x | None -> 0)) + let result = + map m (fun v -> + Some( + match v with + | Some x -> x + | None -> 0 + )) Assert.Equal(Ok(Some 3), get result 0UL 0UL) Assert.Equal(Ok(Some 0), get result 1UL 1UL) @@ -743,10 +771,262 @@ let ``matrix map on empty matrix`` () = [] let ``matrix map nvals updated`` () = let m = - fromCoordinateList(CoordinateList(4UL, 4UL, [ (0UL, 0UL, 3); (1UL, 2UL, 5) ])) + fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 3); (1UL, 2UL, 5) ] + ) + ) Assert.Equal(2UL, uint64 m.nvals) let result = map m (fun _ -> None) Assert.Equal(0UL, uint64 result.nvals) + +[] +let ``matrix mapValues applies only to stored values`` () = + let m = + fromCoordinateList (CoordinateList(4UL, 4UL, [ (0UL, 0UL, 3) ])) + + let result = mapValues m (fun v -> Some(v * 2)) + + Assert.Equal(Ok(Some 6), get result 0UL 0UL) + Assert.Equal(Ok(None), get result 2UL 2UL) + Assert.Equal(Ok(None), get result 0UL 1UL) + Assert.Equal(1UL, uint64 result.nvals) + +[] +let ``matrix mapValues can drop stored values`` () = + let m = + fromCoordinateList (CoordinateList(4UL, 4UL, [ (0UL, 0UL, 3) ])) + + let result = mapValues m (fun _ -> None) + + Assert.Equal(0UL, uint64 result.nvals) + +[] +let ``matrix mapiValues applies only to stored values with indices`` () = + let m = + fromCoordinateList (CoordinateList(4UL, 4UL, [ (1UL, 2UL, 5) ])) + + let result = mapiValues m (fun i j v -> Some(v + int (uint64 i) + int (uint64 j))) + + Assert.Equal(Ok(Some 8), get result 1UL 2UL) + Assert.Equal(Ok(None), get result 0UL 0UL) + Assert.Equal(1UL, uint64 result.nvals) + +[] +let ``matrix mapi expands uniform leaf`` () = + let m = + SparseMatrix( + 4UL, + 4UL, + 4UL, + Storage(4UL, Matrix.qtree.Node(leaf_v 5, leaf_n (), leaf_n (), leaf_n ())) + ) + + let result = + mapi m (fun i j v -> v |> Option.map (fun x -> x + int (uint64 i) + int (uint64 j))) + + Assert.Equal(4UL, uint64 result.nvals) + Assert.Equal(Ok(Some 5), get result 0UL 0UL) + Assert.Equal(Ok(Some 6), get result 0UL 1UL) + Assert.Equal(Ok(Some 6), get result 1UL 0UL) + Assert.Equal(Ok(Some 7), get result 1UL 1UL) + +[] +let ``matrix map2Values applies only where both values present`` () = + let m1 = + fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1); (1UL, 1UL, 2) ] + ) + ) + + let m2 = + fromCoordinateList (CoordinateList(4UL, 4UL, [ (0UL, 0UL, 10) ])) + + match map2Values m1 m2 (fun a b -> Some(a + b + 100)) with + | Error e -> failwithf "unexpected error %A" e + | Ok result -> + Assert.Equal(Ok(Some 111), get result 0UL 0UL) + Assert.Equal(Ok(None), get result 1UL 1UL) + Assert.Equal(1UL, uint64 result.nvals) + +[] +let ``matrix map2AllCells equals map2`` () = + let m1 = + fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1); (1UL, 1UL, 2) ] + ) + ) + + let m2 = + fromCoordinateList (CoordinateList(4UL, 4UL, [ (0UL, 0UL, 10) ])) + + let f a b = + match a, b with + | Some x, Some y -> Some(x + y) + | _ -> None + + Assert.Equal(map2 m1 m2 f, map2AllCells m1 m2 f) + +[] +let ``matrix map2AtLeastOne distinguishes both left right`` () = + let m1 = + fromCoordinateList ( + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 1); (1UL, 1UL, 2) ] + ) + ) + + let m2 = + fromCoordinateList ( + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 10); (2UL, 2UL, 30) ] + ) + ) + + let f = + function + | AtLeastOne.Both(a, b) -> Some(a + b) + | AtLeastOne.Left a -> Some(a * 100) + | AtLeastOne.Right b -> Some(b * -1) + + match map2AtLeastOne m1 m2 f with + | Error e -> failwithf "unexpected error %A" e + | Ok result -> + Assert.Equal(Ok(Some 11), get result 0UL 0UL) + Assert.Equal(Ok(Some 200), get result 1UL 1UL) + Assert.Equal(Ok(Some -30), get result 2UL 2UL) + +[] +let ``matrix map2LeftValues applies where left value present`` () = + let m1 = + fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 1); (1UL, 1UL, 2) ] + ) + ) + + let m2 = + fromCoordinateList (CoordinateList(4UL, 4UL, [ (0UL, 0UL, 10) ])) + + match map2LeftValues m1 m2 (fun a b -> Some(a + (defaultArg b 0))) with + | Error e -> failwithf "unexpected error %A" e + | Ok result -> + Assert.Equal(Ok(Some 11), get result 0UL 0UL) + Assert.Equal(Ok(Some 2), get result 1UL 1UL) + Assert.Equal(2UL, uint64 result.nvals) + +[] +let ``matrix map2i expands uniform leaves on both sides`` () = + let m1 = + SparseMatrix( + 4UL, + 4UL, + 4UL, + Storage(4UL, Matrix.qtree.Node(leaf_v 5, leaf_n (), leaf_n (), leaf_n ())) + ) + + let m2 = + SparseMatrix( + 4UL, + 4UL, + 4UL, + Storage(4UL, Matrix.qtree.Node(leaf_v 10, leaf_n (), leaf_n (), leaf_n ())) + ) + + let f i j a b = + match a, b with + | Some x, Some y -> Some(x + y + int (uint64 i) * 10 + int (uint64 j)) + | _ -> None + + match map2i m1 m2 f with + | Error e -> failwithf "unexpected error %A" e + | Ok result -> + Assert.Equal(4UL, uint64 result.nvals) + Assert.Equal(Ok(Some 15), get result 0UL 0UL) + Assert.Equal(Ok(Some 16), get result 0UL 1UL) + Assert.Equal(Ok(Some 25), get result 1UL 0UL) + Assert.Equal(Ok(Some 26), get result 1UL 1UL) + +[] +let ``matrix map2iValues applies indexed where both values present`` () = + let m1 = + fromCoordinateList (CoordinateList(4UL, 4UL, [ (1UL, 1UL, 2) ])) + + let m2 = + fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (1UL, 1UL, 10); (2UL, 2UL, 20) ] + ) + ) + + let f i j a b = + Some(a + b + int (uint64 i) + int (uint64 j)) + + match map2iValues m1 m2 f with + | Error e -> failwithf "unexpected error %A" e + | Ok result -> + Assert.Equal(Ok(Some 14), get result 1UL 1UL) + Assert.Equal(Ok(None), get result 2UL 2UL) + Assert.Equal(1UL, uint64 result.nvals) + +[] +let ``matrix map2iAtLeastOne passes indices and side`` () = + let m1 = + fromCoordinateList (CoordinateList(2UL, 2UL, [ (0UL, 0UL, 1) ])) + + let m2 = + fromCoordinateList ( + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 10); (1UL, 1UL, 20) ] + ) + ) + + let f i j = + function + | AtLeastOne.Both(a, b) -> Some(a + b + int (uint64 i) + int (uint64 j)) + | AtLeastOne.Left a -> Some(a) + | AtLeastOne.Right b -> Some(b + int (uint64 i) * 100 + int (uint64 j)) + + match map2iAtLeastOne m1 m2 f with + | Error e -> failwithf "unexpected error %A" e + | Ok result -> + Assert.Equal(Ok(Some 11), get result 0UL 0UL) + Assert.Equal(Ok(Some 121), get result 1UL 1UL) + +[] +let ``matrix map2iLeftValues applies indexed where left present`` () = + let m1 = + fromCoordinateList (CoordinateList(2UL, 2UL, [ (1UL, 1UL, 2) ])) + + let m2 = + fromCoordinateList (CoordinateList(2UL, 2UL, [ (1UL, 1UL, 10) ])) + + let f i j a b = + Some(a + (defaultArg b 0) + int (uint64 i) * 10 + int (uint64 j)) + + match map2iLeftValues m1 m2 f with + | Error e -> failwithf "unexpected error %A" e + | Ok result -> + Assert.Equal(Ok(Some 23), get result 1UL 1UL) + Assert.Equal(1UL, uint64 result.nvals) diff --git a/QuadTree/COO.fs b/QuadTree/COO.fs index 26f792c..046086f 100644 --- a/QuadTree/COO.fs +++ b/QuadTree/COO.fs @@ -51,19 +51,78 @@ let cooUpdate Ok(CoordinateList(coo.nrows, coo.ncols, List.rev acc)) -let cooMap (coo: CoordinateList<'a>) f = - let updatedList = coo.list |> List.map (fun (i, j, v) -> (i, j, f (Some v))) - +let private applyBinary + (op: BinaryOp<'a, 'b, 'c>) + (i: uint64) + (j: uint64) + (v1: Option<'a>) + (v2: Option<'b>) + : Option<'c> = + match op with + | BinaryOp.ValuesOnly f -> + match v1, v2 with + | Some a, Some b -> f a b + | _ -> None + | BinaryOp.ValuesOnlyIndexed f -> + match v1, v2 with + | Some a, Some b -> f i j a b + | _ -> None + | BinaryOp.AllCells f -> f v1 v2 + | BinaryOp.AllCellsIndexed f -> f i j v1 v2 + | BinaryOp.AtLeastOneValue f -> + match v1, v2 with + | Some a, Some b -> f (AtLeastOne.Both(a, b)) + | Some a, None -> f (AtLeastOne.Left a) + | None, Some b -> f (AtLeastOne.Right b) + | None, None -> None + | BinaryOp.AtLeastOneValueIndexed f -> + match v1, v2 with + | Some a, Some b -> f i j (AtLeastOne.Both(a, b)) + | Some a, None -> f i j (AtLeastOne.Left a) + | None, Some b -> f i j (AtLeastOne.Right b) + | None, None -> None + | BinaryOp.LeftValuesOnly f -> + match v1 with + | Some a -> f a v2 + | None -> None + | BinaryOp.LeftValuesOnlyIndexed f -> + match v1 with + | Some a -> f i j a v2 + | None -> None + +let private cooMapInner (coo: CoordinateList<'a>) (op: UnaryOp<'a, 'b>) : CoordinateList<'b> = let result = - match f None with - | None -> - updatedList - |> List.choose (fun (i, j, v) -> v |> Option.map (fun v -> (i, j, v))) - | Some fnone -> - let lookup = - updatedList - |> List.map (fun (i, j, v) -> ((i, j), v)) - |> Map.ofList + match op with + | UnaryOp.ValuesOnly f -> + coo.list + |> List.choose (fun (i, j, v) -> f v |> Option.map (fun r -> (i, j, r))) + | UnaryOp.ValuesOnlyIndexed f -> + coo.list + |> List.choose (fun (i, j, v) -> f i j v |> Option.map (fun r -> (i, j, r))) + | UnaryOp.AllCells f -> + match f None with + | None -> + coo.list + |> List.choose (fun (i, j, v) -> f (Some v) |> Option.map (fun r -> (i, j, r))) + | Some fnone -> + let lookup = coo.list |> List.map (fun (i, j, v) -> ((i, j), v)) |> Map.ofList + + [ for i in range (uint64 coo.nrows) do + let ri = i * 1UL + + for j in range (uint64 coo.ncols) do + let cj = j * 1UL + + let res = + match Map.tryFind (ri, cj) lookup with + | Some value -> f (Some value) + | None -> Some fnone + + match res with + | Some value -> yield (ri, cj, value) + | None -> () ] + | UnaryOp.AllCellsIndexed f -> + let mutable rest = coo.list [ for i in range (uint64 coo.nrows) do let ri = i * 1UL @@ -71,127 +130,152 @@ let cooMap (coo: CoordinateList<'a>) f = for j in range (uint64 coo.ncols) do let cj = j * 1UL - match Map.tryFind (ri, cj) lookup with - | Some(Some value) -> yield (ri, cj, value) - | Some None -> () - | None -> yield (ri, cj, fnone) ] - - CoordinateList(coo.nrows, coo.ncols, result) - -let cooMapi (coo: CoordinateList<'a>) f = - let lookup = - coo.list - |> List.map (fun (i, j, v) -> ((i, j), v)) - |> Map.ofList - - let result = - [ for i in range (uint64 coo.nrows) do - let ri = i * 1UL - - for j in range (uint64 coo.ncols) do - let cj = j * 1UL + let value = + match rest with + | (ei, ej, ev) :: tail when ei = ri && ej = cj -> + rest <- tail + Some ev + | _ -> None - let res = f ri cj (Map.tryFind (ri, cj) lookup) - - match res with - | Some value -> yield (ri, cj, value) - | None -> () ] + match f ri cj value with + | Some value -> yield (ri, cj, value) + | None -> () ] CoordinateList(coo.nrows, coo.ncols, result) -let cooMap2 (coo1: CoordinateList<'a>) (coo2: CoordinateList<'b>) f = +let private mergeBinary (l1: COOEntry<'a> list) (l2: COOEntry<'b> list) (op: BinaryOp<'a, 'b, 'c>) : COOEntry<'c> list = let mutable acc = [] - let mutable l1 = coo1.list - let mutable l2 = coo2.list + let mutable rest1 = l1 + let mutable rest2 = l2 - while l1 <> [] || l2 <> [] do - match l1, l2 with + let emit i j v1 v2 = + match applyBinary op i j v1 v2 with + | Some r -> acc <- (i, j, r) :: acc + | None -> () + + while rest1 <> [] || rest2 <> [] do + match rest1, rest2 with | [], [] -> () - | (i1, j1, v1) :: t1, [] -> - let r = f (Some v1) None - acc <- (i1, j1, r) :: acc - l1 <- t1 - | [], (i2, j2, v2) :: t2 -> - let r = f None (Some v2) - acc <- (i2, j2, r) :: acc - l2 <- t2 + | (i, j, v1) :: t1, [] -> + emit i j (Some v1) None + rest1 <- t1 + | [], (i, j, v2) :: t2 -> + emit i j None (Some v2) + rest2 <- t2 | (i1, j1, v1) :: t1, (i2, j2, v2) :: t2 -> if i1 = i2 && j1 = j2 then - let r = f (Some v1) (Some v2) - acc <- (i1, j1, r) :: acc - l1 <- t1 - l2 <- t2 + emit i1 j1 (Some v1) (Some v2) + rest1 <- t1 + rest2 <- t2 elif (i1, j1) < (i2, j2) then - let r = f (Some v1) None - acc <- (i1, j1, r) :: acc - l1 <- t1 + emit i1 j1 (Some v1) None + rest1 <- t1 else - let r = f None (Some v2) - acc <- (i2, j2, r) :: acc - l2 <- t2 + emit i2 j2 None (Some v2) + rest2 <- t2 - let updatedList = List.rev acc + List.rev acc - let result = - match f None None with - | None -> - updatedList - |> List.choose (fun (i, j, v) -> v |> Option.map (fun v -> (i, j, v))) - | Some fnone -> - let lookup = - updatedList - |> List.map (fun (i, j, v) -> ((i, j), v)) - |> Map.ofList - - [ for i in range (uint64 coo1.nrows) do - let ri = i * 1UL +let private cooMap2Inner + (coo1: CoordinateList<'a>) + (coo2: CoordinateList<'b>) + (op: BinaryOp<'a, 'b, 'c>) + : Result, Error> = + if uint64 coo1.nrows <> uint64 coo2.nrows || uint64 coo1.ncols <> uint64 coo2.ncols then + Error Error.InconsistentSizeOfArguments + else + let nrows = coo1.nrows + let ncols = coo1.ncols - for j in range (uint64 coo1.ncols) do - let cj = j * 1UL + let result = + match op with + | BinaryOp.AllCells f -> + match f None None with + | None -> mergeBinary coo1.list coo2.list op + | Some _ -> + let lookup1 = coo1.list |> List.map (fun (i, j, v) -> ((i, j), v)) |> Map.ofList + + let lookup2 = coo2.list |> List.map (fun (i, j, v) -> ((i, j), v)) |> Map.ofList + + [ for i in range (uint64 nrows) do + let ri = i * 1UL + + for j in range (uint64 ncols) do + let cj = j * 1UL - match Map.tryFind (ri, cj) lookup with - | Some(Some value) -> yield (ri, cj, value) - | Some None -> () - | None -> yield (ri, cj, fnone) ] + match f (Map.tryFind (ri, cj) lookup1) (Map.tryFind (ri, cj) lookup2) with + | Some value -> yield (ri, cj, value) + | None -> () ] + | BinaryOp.AllCellsIndexed f -> + let mutable rest1 = coo1.list + let mutable rest2 = coo2.list - CoordinateList(coo1.nrows, coo1.ncols, result) + [ for i in range (uint64 nrows) do + let ri = i * 1UL + + for j in range (uint64 ncols) do + let cj = j * 1UL + + let v1 = + match rest1 with + | (ei, ej, ev) :: tail when ei = ri && ej = cj -> + rest1 <- tail + Some ev + | _ -> None + + let v2 = + match rest2 with + | (ei, ej, ev) :: tail when ei = ri && ej = cj -> + rest2 <- tail + Some ev + | _ -> None + + match f ri cj v1 v2 with + | Some value -> yield (ri, cj, value) + | None -> () ] + | _ -> mergeBinary coo1.list coo2.list op + + CoordinateList(nrows, ncols, result) |> Ok + +let cooMap (coo: CoordinateList<'a>) f = cooMapInner coo (UnaryOp.AllCells f) + +let cooMapValues (coo: CoordinateList<'a>) f = cooMapInner coo (UnaryOp.ValuesOnly f) + +let cooMapi (coo: CoordinateList<'a>) f = + cooMapInner coo (UnaryOp.AllCellsIndexed f) + +let cooMapiValues (coo: CoordinateList<'a>) f = + cooMapInner coo (UnaryOp.ValuesOnlyIndexed f) + +let cooMap2 (coo1: CoordinateList<'a>) (coo2: CoordinateList<'b>) f = + cooMap2Inner coo1 coo2 (BinaryOp.AllCells f) + +let cooMap2Values (coo1: CoordinateList<'a>) (coo2: CoordinateList<'b>) f = + cooMap2Inner coo1 coo2 (BinaryOp.ValuesOnly f) + +let cooMap2AllCells (coo1: CoordinateList<'a>) (coo2: CoordinateList<'b>) f = + cooMap2Inner coo1 coo2 (BinaryOp.AllCells f) + +let cooMap2AtLeastOne (coo1: CoordinateList<'a>) (coo2: CoordinateList<'b>) f = + cooMap2Inner coo1 coo2 (BinaryOp.AtLeastOneValue f) + +let cooMap2LeftValues (coo1: CoordinateList<'a>) (coo2: CoordinateList<'b>) f = + cooMap2Inner coo1 coo2 (BinaryOp.LeftValuesOnly f) let cooMap2i (coo1: CoordinateList<'a>) (coo2: CoordinateList<'b>) f = - let mutable acc = [] - let mutable l1 = coo1.list - let mutable l2 = coo2.list + cooMap2Inner coo1 coo2 (BinaryOp.AllCellsIndexed f) - while l1 <> [] || l2 <> [] do - match l1, l2 with - | [], [] -> () - | (i1, j1, v1) :: t1, [] -> - let r = f i1 j1 (Some v1) None - acc <- (i1, j1, r) :: acc - l1 <- t1 - | [], (i2, j2, v2) :: t2 -> - let r = f i2 j2 None (Some v2) - acc <- (i2, j2, r) :: acc - l2 <- t2 - | (i1, j1, v1) :: t1, (i2, j2, v2) :: t2 -> - if i1 = i2 && j1 = j2 then - let r = f i1 j1 (Some v1) (Some v2) - acc <- (i1, j1, r) :: acc - l1 <- t1 - l2 <- t2 - elif (i1, j1) < (i2, j2) then - let r = f i1 j1 (Some v1) None - acc <- (i1, j1, r) :: acc - l1 <- t1 - else - let r = f i2 j2 None (Some v2) - acc <- (i2, j2, r) :: acc - l2 <- t2 +let cooMap2iValues (coo1: CoordinateList<'a>) (coo2: CoordinateList<'b>) f = + cooMap2Inner coo1 coo2 (BinaryOp.ValuesOnlyIndexed f) - let result = - List.rev acc - |> List.choose (fun (i, j, v) -> v |> Option.map (fun v -> (i, j, v))) +let cooMap2iAllCells (coo1: CoordinateList<'a>) (coo2: CoordinateList<'b>) f = + cooMap2Inner coo1 coo2 (BinaryOp.AllCellsIndexed f) + +let cooMap2iAtLeastOne (coo1: CoordinateList<'a>) (coo2: CoordinateList<'b>) f = + cooMap2Inner coo1 coo2 (BinaryOp.AtLeastOneValueIndexed f) - CoordinateList(coo1.nrows, coo1.ncols, result) +let cooMap2iLeftValues (coo1: CoordinateList<'a>) (coo2: CoordinateList<'b>) f = + cooMap2Inner coo1 coo2 (BinaryOp.LeftValuesOnlyIndexed f) let mxmcoo (op_add: 'c option -> 'c option -> 'c option) diff --git a/QuadTree/Matrix.fs b/QuadTree/Matrix.fs index a75df31..74d6f6f 100644 --- a/QuadTree/Matrix.fs +++ b/QuadTree/Matrix.fs @@ -227,187 +227,275 @@ let set let nvals = uint64 (int64 matrix.nvals + deltaNNZ) * 1UL Ok(SparseMatrix(matrix.nrows, matrix.ncols, nvals, Storage(matrix.storage.size, storage))) -let map (matrix: SparseMatrix<_>) f = - let rec inner (size: uint64) matrix = - match matrix with - | Leaf(Dummy) -> Leaf(Dummy), 0UL - | Leaf(UserValue(v)) -> - let res = f v - - let nnz = - match res with - | None -> 0UL - | _ -> (uint64 size) * (uint64 size) * 1UL +type UnaryOp<'a, 'b> = + | ValuesOnly of ('a -> Option<'b>) + | ValuesOnlyIndexed of (uint64 -> uint64 -> 'a -> Option<'b>) + | AllCells of (Option<'a> -> Option<'b>) + | AllCellsIndexed of (uint64 -> uint64 -> Option<'a> -> Option<'b>) + +let private mapInner (matrix: SparseMatrix<'a>) (op: UnaryOp<'a, 'b>) : SparseMatrix<'b> = + let rec inner + (prow: uint64) + (pcol: uint64) + (size: uint64) + (tree: qtree>) + : qtree> * uint64 = + match tree with + | Node(nw, ne, sw, se) -> + let halfSize = size / 2UL - Leaf(UserValue(res)), nnz - | Node(x1, x2, x3, x4) -> - let new_size = size / 2UL + let (nwR, nwC), (neR, neC), (swR, swC), (seR, seC) = + getQuadrantCoords (prow, pcol) (uint64 halfSize) - let t1, nvals1 = inner new_size x1 - let t2, nvals2 = inner new_size x2 - let t3, nvals3 = inner new_size x3 - let t4, nvals4 = inner new_size x4 + let t1, nvals1 = inner nwR nwC halfSize nw + let t2, nvals2 = inner neR neC halfSize ne + let t3, nvals3 = inner swR swC halfSize sw + let t4, nvals4 = inner seR seC halfSize se mkNode t1 t2 t3 t4, nvals1 + nvals2 + nvals3 + nvals4 + | Leaf(Dummy) -> Leaf(Dummy), 0UL + | Leaf(UserValue(v)) -> + match op with + | UnaryOp.ValuesOnly f -> + match v with + | None -> Leaf(UserValue(None)), 0UL + | Some v' -> + let res = f v' + + let nvals = + if res.IsSome then + (uint64 size) * (uint64 size) * 1UL + else + 0UL + + Leaf(UserValue(res)), nvals + | UnaryOp.ValuesOnlyIndexed f -> + match v with + | None -> Leaf(UserValue(None)), 0UL + | Some v' -> + if size = 1UL then + let res = f prow pcol v' + let nvals = if res.IsSome then 1UL else 0UL + Leaf(UserValue(res)), nvals + else + let halfSize = size / 2UL + + let (nwR, nwC), (neR, neC), (swR, swC), (seR, seC) = + getQuadrantCoords (prow, pcol) (uint64 halfSize) + + let t1, nvals1 = inner nwR nwC halfSize (Leaf(UserValue(v))) + let t2, nvals2 = inner neR neC halfSize (Leaf(UserValue(v))) + let t3, nvals3 = inner swR swC halfSize (Leaf(UserValue(v))) + let t4, nvals4 = inner seR seC halfSize (Leaf(UserValue(v))) + mkNode t1 t2 t3 t4, nvals1 + nvals2 + nvals3 + nvals4 + | UnaryOp.AllCells f -> + let res = f v + + let nvals = + if res.IsSome then + (uint64 size) * (uint64 size) * 1UL + else + 0UL + + Leaf(UserValue(res)), nvals + | UnaryOp.AllCellsIndexed f -> + if size = 1UL then + let res = f prow pcol v + let nvals = if res.IsSome then 1UL else 0UL + Leaf(UserValue(res)), nvals + else + let halfSize = size / 2UL + + let (nwR, nwC), (neR, neC), (swR, swC), (seR, seC) = + getQuadrantCoords (prow, pcol) (uint64 halfSize) + + let t1, nvals1 = inner nwR nwC halfSize (Leaf(UserValue(v))) + let t2, nvals2 = inner neR neC halfSize (Leaf(UserValue(v))) + let t3, nvals3 = inner swR swC halfSize (Leaf(UserValue(v))) + let t4, nvals4 = inner seR seC halfSize (Leaf(UserValue(v))) + mkNode t1 t2 t3 t4, nvals1 + nvals2 + nvals3 + nvals4 - let storage, nvals = inner matrix.storage.size matrix.storage.data + let storage, nvals = + inner 0UL 0UL matrix.storage.size matrix.storage.data SparseMatrix(matrix.nrows, matrix.ncols, nvals, Storage(matrix.storage.size, storage)) -let map2 (matrix1: SparseMatrix<_>) (matrix2: SparseMatrix<_>) f = - let rec inner (size: uint64) matrix1 matrix2 = - let _do x1 x2 x3 x4 y1 y2 y3 y4 = - let new_size = size / 2UL +let map (matrix: SparseMatrix<_>) f = mapInner matrix (UnaryOp.AllCells f) + +let mapValues (matrix: SparseMatrix<'a>) f = mapInner matrix (UnaryOp.ValuesOnly f) + +type AtLeastOne<'a, 'b> = + | Both of 'a * 'b + | Left of 'a + | Right of 'b + +type BinaryOp<'a, 'b, 'c> = + | ValuesOnly of ('a -> 'b -> Option<'c>) + | ValuesOnlyIndexed of (uint64 -> uint64 -> 'a -> 'b -> Option<'c>) + | AllCells of (Option<'a> -> Option<'b> -> Option<'c>) + | AllCellsIndexed of (uint64 -> uint64 -> Option<'a> -> Option<'b> -> Option<'c>) + | AtLeastOneValue of (AtLeastOne<'a, 'b> -> Option<'c>) + | AtLeastOneValueIndexed of (uint64 -> uint64 -> AtLeastOne<'a, 'b> -> Option<'c>) + | LeftValuesOnly of ('a -> Option<'b> -> Option<'c>) + | LeftValuesOnlyIndexed of (uint64 -> uint64 -> 'a -> Option<'b> -> Option<'c>) + +let private applyBinary + (op: BinaryOp<'a, 'b, 'c>) + (prow: uint64) + (pcol: uint64) + (v1: Option<'a>) + (v2: Option<'b>) + : Option<'c> = + match op with + | BinaryOp.ValuesOnly f -> + match v1, v2 with + | Some a, Some b -> f a b + | _ -> None + | BinaryOp.ValuesOnlyIndexed f -> + match v1, v2 with + | Some a, Some b -> f prow pcol a b + | _ -> None + | BinaryOp.AllCells f -> f v1 v2 + | BinaryOp.AllCellsIndexed f -> f prow pcol v1 v2 + | BinaryOp.AtLeastOneValue f -> + match v1, v2 with + | Some a, Some b -> f (AtLeastOne.Both(a, b)) + | Some a, None -> f (AtLeastOne.Left a) + | None, Some b -> f (AtLeastOne.Right b) + | None, None -> None + | BinaryOp.AtLeastOneValueIndexed f -> + match v1, v2 with + | Some a, Some b -> f prow pcol (AtLeastOne.Both(a, b)) + | Some a, None -> f prow pcol (AtLeastOne.Left a) + | None, Some b -> f prow pcol (AtLeastOne.Right b) + | None, None -> None + | BinaryOp.LeftValuesOnly f -> + match v1 with + | Some a -> f a v2 + | None -> None + | BinaryOp.LeftValuesOnlyIndexed f -> + match v1 with + | Some a -> f prow pcol a v2 + | None -> None + +let private isIndexedBinary (op: BinaryOp<'a, 'b, 'c>) = + match op with + | BinaryOp.ValuesOnlyIndexed _ + | BinaryOp.AllCellsIndexed _ + | BinaryOp.AtLeastOneValueIndexed _ + | BinaryOp.LeftValuesOnlyIndexed _ -> true + | _ -> false + +let private map2Inner + (matrix1: SparseMatrix<'a>) + (matrix2: SparseMatrix<'b>) + (op: BinaryOp<'a, 'b, 'c>) + : Result, Error> = + let rec inner + (prow: uint64) + (pcol: uint64) + (size: uint64) + (tree1: qtree>) + (tree2: qtree>) + : Result> * uint64, Error> = + let split + (x1: qtree>) + (x2: qtree>) + (x3: qtree>) + (x4: qtree>) + (y1: qtree>) + (y2: qtree>) + (y3: qtree>) + (y4: qtree>) + = + let halfSize = size / 2UL - match (inner new_size x1 y1), (inner new_size x2 y2), (inner new_size x3 y3), (inner new_size x4 y4) with - | Ok((new_t1, nvals1)), Ok((new_t2, nvals2)), Ok((new_t3, nvals3)), Ok((new_t4, nvals4)) -> - ((mkNode new_t1 new_t2 new_t3 new_t4), nvals1 + nvals2 + nvals3 + nvals4) |> Ok - | Error(e), _, _, _ - | _, Error(e), _, _ - | _, _, Error(e), _ - | _, _, _, Error(e) -> Error(e) + let (nwR, nwC), (neR, neC), (swR, swC), (seR, seC) = + getQuadrantCoords (prow, pcol) (uint64 halfSize) - match (matrix1, matrix2) with + match + (inner nwR nwC halfSize x1 y1), + (inner neR neC halfSize x2 y2), + (inner swR swC halfSize x3 y3), + (inner seR seC halfSize x4 y4) + with + | Ok(t1, nvals1), Ok(t2, nvals2), Ok(t3, nvals3), Ok(t4, nvals4) -> + Ok(mkNode t1 t2 t3 t4, nvals1 + nvals2 + nvals3 + nvals4) + | Error e, _, _, _ + | _, Error e, _, _ + | _, _, Error e, _ + | _, _, _, Error e -> Error e + + match tree1, tree2 with + | Node(x1, x2, x3, x4), Node(y1, y2, y3, y4) -> split x1 x2 x3 x4 y1 y2 y3 y4 + | Node(x1, x2, x3, x4), Leaf(v2) -> split x1 x2 x3 x4 (Leaf(v2)) (Leaf(v2)) (Leaf(v2)) (Leaf(v2)) + | Leaf(v1), Node(y1, y2, y3, y4) -> split (Leaf(v1)) (Leaf(v1)) (Leaf(v1)) (Leaf(v1)) y1 y2 y3 y4 | Leaf(Dummy), Leaf(Dummy) -> Ok(Leaf(Dummy), 0UL) | Leaf(UserValue(v1)), Leaf(UserValue(v2)) -> - let res = f v1 v2 - - let nnz = - match res with - | None -> 0UL - | _ -> (uint64 size) * (uint64 size) * 1UL + if size > 1UL && isIndexedBinary op then + split + (Leaf(UserValue(v1))) + (Leaf(UserValue(v1))) + (Leaf(UserValue(v1))) + (Leaf(UserValue(v1))) + (Leaf(UserValue(v2))) + (Leaf(UserValue(v2))) + (Leaf(UserValue(v2))) + (Leaf(UserValue(v2))) + else + let res = applyBinary op prow pcol v1 v2 - (Leaf(UserValue(res)), nnz) |> Ok + let nnz = + if res.IsSome then + (uint64 size) * (uint64 size) * 1UL + else + 0UL - | Node(x1, x2, x3, x4), Node(y1, y2, y3, y4) -> _do x1 x2 x3 x4 y1 y2 y3 y4 - | Node(x1, x2, x3, x4), Leaf(v) -> _do x1 x2 x3 x4 matrix2 matrix2 matrix2 matrix2 - | Leaf(v), Node(x1, x2, x3, x4) -> _do matrix1 matrix1 matrix1 matrix1 x1 x2 x3 x4 - | (x, y) -> Error Error.InconsistentStructureOfStorages + Ok(Leaf(UserValue(res)), nnz) + | _ -> Error Error.InconsistentStructureOfStorages if matrix1.nrows = matrix2.nrows && matrix1.ncols = matrix2.ncols then - match inner matrix1.storage.size matrix1.storage.data matrix2.storage.data with - | Error x -> Error x - | Ok(storage, nvals) -> - (SparseMatrix(matrix1.nrows, matrix1.ncols, nvals, (Storage(matrix1.storage.size, storage)))) - |> Ok + inner 0UL 0UL matrix1.storage.size matrix1.storage.data matrix2.storage.data + |> Result.map (fun (storage, nvals) -> + SparseMatrix(matrix1.nrows, matrix1.ncols, nvals, Storage(matrix1.storage.size, storage))) else Error Error.InconsistentSizeOfArguments -let map2i (matrix1: SparseMatrix<_>) (matrix2: SparseMatrix<_>) f = - let rec inner (prow: uint64) (pcol: uint64) (size: uint64) matrix1 matrix2 = - match (matrix1, matrix2) with - | Node(x1, x2, x3, x4), Node(y1, y2, y3, y4) -> - let halfSize = size / 2UL +let map2 (matrix1: SparseMatrix<'a>) (matrix2: SparseMatrix<'b>) f = + map2Inner matrix1 matrix2 (BinaryOp.AllCells f) - let (nwR, nwC), (neR, neC), (swR, swC), (seR, seC) = - getQuadrantCoords (prow, pcol) (uint64 halfSize) +let map2Values (matrix1: SparseMatrix<'a>) (matrix2: SparseMatrix<'b>) f = + map2Inner matrix1 matrix2 (BinaryOp.ValuesOnly f) - let t1, nvals1 = inner nwR nwC halfSize x1 y1 - let t2, nvals2 = inner neR neC halfSize x2 y2 - let t3, nvals3 = inner swR swC halfSize x3 y3 - let t4, nvals4 = inner seR seC halfSize x4 y4 - (mkNode t1 t2 t3 t4), nvals1 + nvals2 + nvals3 + nvals4 - | Node(x1, x2, x3, x4), Leaf(v2) -> - let halfSize = size / 2UL +let map2AllCells (matrix1: SparseMatrix<'a>) (matrix2: SparseMatrix<'b>) f = + map2Inner matrix1 matrix2 (BinaryOp.AllCells f) - let (nwR, nwC), (neR, neC), (swR, swC), (seR, seC) = - getQuadrantCoords (prow, pcol) (uint64 halfSize) +let map2AtLeastOne (matrix1: SparseMatrix<'a>) (matrix2: SparseMatrix<'b>) f = + map2Inner matrix1 matrix2 (BinaryOp.AtLeastOneValue f) - let t1, nvals1 = inner nwR nwC halfSize x1 (Leaf(v2)) - let t2, nvals2 = inner neR neC halfSize x2 (Leaf(v2)) - let t3, nvals3 = inner swR swC halfSize x3 (Leaf(v2)) - let t4, nvals4 = inner seR seC halfSize x4 (Leaf(v2)) - (mkNode t1 t2 t3 t4), nvals1 + nvals2 + nvals3 + nvals4 - | Leaf(v1), Node(y1, y2, y3, y4) -> - let halfSize = size / 2UL +let map2LeftValues (matrix1: SparseMatrix<'a>) (matrix2: SparseMatrix<'b>) f = + map2Inner matrix1 matrix2 (BinaryOp.LeftValuesOnly f) - let (nwR, nwC), (neR, neC), (swR, swC), (seR, seC) = - getQuadrantCoords (prow, pcol) (uint64 halfSize) - - let t1, nvals1 = inner nwR nwC halfSize (Leaf(v1)) y1 - let t2, nvals2 = inner neR neC halfSize (Leaf(v1)) y2 - let t3, nvals3 = inner swR swC halfSize (Leaf(v1)) y3 - let t4, nvals4 = inner seR seC halfSize (Leaf(v1)) y4 - (mkNode t1 t2 t3 t4), nvals1 + nvals2 + nvals3 + nvals4 - | Leaf(Dummy), Leaf(Dummy) -> Leaf(Dummy), 0UL - | Leaf(UserValue(v1)), Leaf(UserValue(v2)) -> - let res = f prow pcol v1 v2 - - let nnz = - match res with - | Some _ -> 1UL - | None -> 0UL +let map2i (matrix1: SparseMatrix<'a>) (matrix2: SparseMatrix<'b>) f = + map2Inner matrix1 matrix2 (BinaryOp.AllCellsIndexed f) - Leaf(UserValue(res)), nnz - | Leaf(UserValue(v)), Leaf(Dummy) -> - let res = f prow pcol v None +let map2iValues (matrix1: SparseMatrix<'a>) (matrix2: SparseMatrix<'b>) f = + map2Inner matrix1 matrix2 (BinaryOp.ValuesOnlyIndexed f) - let nnz = - match res with - | Some _ -> 1UL - | None -> 0UL +let map2iAllCells (matrix1: SparseMatrix<'a>) (matrix2: SparseMatrix<'b>) f = + map2Inner matrix1 matrix2 (BinaryOp.AllCellsIndexed f) - Leaf(UserValue(res)), nnz - | Leaf(Dummy), Leaf(UserValue(v)) -> - let res = f prow pcol None v +let map2iAtLeastOne (matrix1: SparseMatrix<'a>) (matrix2: SparseMatrix<'b>) f = + map2Inner matrix1 matrix2 (BinaryOp.AtLeastOneValueIndexed f) - let nnz = - match res with - | Some _ -> 1UL - | None -> 0UL - - Leaf(UserValue(res)), nnz - - if matrix1.nrows = matrix2.nrows && matrix1.ncols = matrix2.ncols then - let storage, nvals = - inner 0UL 0UL matrix1.storage.size matrix1.storage.data matrix2.storage.data - - SparseMatrix(matrix1.nrows, matrix1.ncols, nvals, (Storage(matrix1.storage.size, storage))) - |> Ok - else - Error Error.InconsistentSizeOfArguments +let map2iLeftValues (matrix1: SparseMatrix<'a>) (matrix2: SparseMatrix<'b>) f = + map2Inner matrix1 matrix2 (BinaryOp.LeftValuesOnlyIndexed f) let mapi (matrix: SparseMatrix<'a>) f = - let rec inner (prow: uint64) (pcol: uint64) (size: uint64) matrix = - match matrix with - | Node(x1, x2, x3, x4) -> - let halfSize = size / 2UL + mapInner matrix (UnaryOp.AllCellsIndexed f) - let (nwR, nwC), (neR, neC), (swR, swC), (seR, seC) = - getQuadrantCoords (prow, pcol) (uint64 halfSize) - - let t1, nvals1 = inner nwR nwC halfSize x1 - let t2, nvals2 = inner neR neC halfSize x2 - let t3, nvals3 = inner swR swC halfSize x3 - let t4, nvals4 = inner seR seC halfSize x4 - (mkNode t1 t2 t3 t4), nvals1 + nvals2 + nvals3 + nvals4 - | Leaf(Dummy) -> Leaf(Dummy), 0UL - | Leaf(UserValue(v)) -> - if size = 1UL then - let res = f prow pcol v - - let nnz = - match res with - | Some _ -> 1UL - | None -> 0UL - - Leaf(UserValue(res)), nnz - else - let halfSize = size / 2UL - - let (nwR, nwC), (neR, neC), (swR, swC), (seR, seC) = - getQuadrantCoords (prow, pcol) (uint64 halfSize) - - let t1, nvals1 = inner nwR nwC halfSize (Leaf(UserValue(v))) - let t2, nvals2 = inner neR neC halfSize (Leaf(UserValue(v))) - let t3, nvals3 = inner swR swC halfSize (Leaf(UserValue(v))) - let t4, nvals4 = inner seR seC halfSize (Leaf(UserValue(v))) - (mkNode t1 t2 t3 t4), nvals1 + nvals2 + nvals3 + nvals4 - - let storage, nvals = - inner 0UL 0UL matrix.storage.size matrix.storage.data - - SparseMatrix(matrix.nrows, matrix.ncols, nvals, (Storage(matrix.storage.size, storage))) +let mapiValues (matrix: SparseMatrix<'a>) f = + mapInner matrix (UnaryOp.ValuesOnlyIndexed f) let foldAssociative (folder: 'T option -> 'T option -> 'T option) (state: 'T option) (matrix: SparseMatrix<'T>) = let rec traverse tree (size: uint64) (state: 'T option) =