diff --git a/QuadTree.Tests/Tests.Matrix.fs b/QuadTree.Tests/Tests.Matrix.fs index 816b4a2..74e4c42 100644 --- a/QuadTree.Tests/Tests.Matrix.fs +++ b/QuadTree.Tests/Tests.Matrix.fs @@ -642,3 +642,1686 @@ let ``Fold sum`` () = let actual = foldAssociative op_add None m1 |> Option.get Assert.Equal(expected, actual) + +[] +let ``fromCoordinateList with out-of-range coordinates throws exception`` () = + let coo = + CoordinateList(6UL, 6UL, [ (9UL, 9UL, 13) ]) + + Assert.Throws(fun () -> fromCoordinateList coo |> ignore) + +[] +let ``fromCoordinateList with unsorted coordinates works correctly`` () = + let coo = + CoordinateList( + 7UL, + 7UL, + [ (6UL, 6UL, 10) + (1UL, 2UL, 8) + (1UL, 1UL, 100) ] + ) + + let matrix = fromCoordinateList coo + let result = toCoordinateList matrix + + Assert.Equal( + CoordinateList( + 7UL, + 7UL, + [ (1UL, 1UL, 100) + (1UL, 2UL, 8) + (6UL, 6UL, 10) ] + ), + result + ) + +[] +let ``fromCoordinateList with duplicate indices returns the last of them`` () = + let coo = + CoordinateList( + 3UL, + 3UL, + [ (1UL, 1UL, 33); (1UL, 1UL, 100) ] + ) + + let matrix = fromCoordinateList coo + let result = toCoordinateList matrix + Assert.Equal(CoordinateList(3UL, 3UL, [ (1UL, 1UL, 100) ]), result) + +[] +let ``fromCoordinateList with zero size throws Exception`` () = + let coo = + CoordinateList( + 0UL, + 0UL, + [ (33UL, 33UL, 33); (39UL, 39UL, 1) ] + ) + + Assert.Throws(fun () -> fromCoordinateList coo |> ignore) + +[] +let ``map works on square matrix`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 4UL, + 4UL, + [ (1UL, 1UL, 22); (2UL, 3UL, 37) ] + ) + ) + + let result = + map matrix (fun (x: int option) -> + match x with + | Some(v) -> Some(v + 9) + | None -> None) + + let coo = toCoordinateList result + + Assert.Equal( + CoordinateList( + 4UL, + 4UL, + [ (1UL, 1UL, 31); (2UL, 3UL, 46) ] + ), + coo + ) + +[] +let ``map works on rectangular matrix`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 6UL, + [ (1UL, 1UL, 21); (4UL, 3UL, 36) ] + ) + ) + + let result = + map matrix (fun (x: int option) -> + match x with + | Some(v) -> Some(v / 3) + | None -> None) + + let coo = toCoordinateList result + + Assert.Equal( + CoordinateList( + 5UL, + 6UL, + [ (1UL, 1UL, 7); (4UL, 3UL, 12) ] + ), + coo + ) + +[] +let ``map on empty matrix returns empty matrix`` () = + let matrix = fromCoordinateList (CoordinateList(0UL, 0UL, [])) + + let result = + map matrix (fun (x: int option) -> + match x with + | Some(v) -> Some(v * 5) + | None -> None) + + let coo = toCoordinateList result + Assert.Equal(CoordinateList(0UL, 0UL, []), coo) + +[] +let ``map with function that turns all the elements into zeros`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 7UL, + [ (1UL, 1UL, 5) + (4UL, 1UL, 17) + (4UL, 6UL, 33) ] + ) + ) + + let result = map matrix (fun _ -> Some(0)) + let coo = toCoordinateList result + + let expectedData = + [ for i in 0UL .. 4UL do + for j in 0UL .. 6UL -> (i * 1UL, j * 1UL, 0) ] + + let expected = CoordinateList(5UL, 7UL, expectedData) + Assert.Equal(expected, coo) + +[] +let ``map with function that turns all the elements to None`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 7UL, + [ (1UL, 1UL, 5) + (4UL, 1UL, 17) + (4UL, 6UL, 33) ] + ) + ) + + let result = map matrix (fun _ -> None) + let coo = toCoordinateList result + Assert.Equal(CoordinateList(5UL, 7UL, []), coo) + +[] +let ``map can change type from int to string`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 3UL, + 5UL, + [ (2UL, 1UL, 17); (2UL, 4UL, 33) ] + ) + ) + + let result = + map matrix (fun (x: int option) -> + match x with + | Some v -> Some(sprintf "str %d" v) + | None -> None) + + let coo = toCoordinateList result + + Assert.Equal( + CoordinateList( + 3UL, + 5UL, + [ (2UL, 1UL, "str 17") + (2UL, 4UL, "str 33") ] + ), + coo + ) + +[] +let ``mapi works with row index and col index on square matrix`` () = + let coo = + CoordinateList( + 4UL, + 4UL, + [ (1UL, 1UL, 12); (2UL, 3UL, 13) ] + ) + + let matrix = fromCoordinateList coo + + let result = + mapi matrix (fun (i: uint64) (j: uint64) (x: int option) -> + match x with + | Some v -> Some(v * int (i) + int (j)) + | None -> None) + + let actual = toCoordinateList result + + Assert.Equal( + CoordinateList( + 4UL, + 4UL, + [ (1UL, 1UL, 13); (2UL, 3UL, 29) ] + ), + actual + ) + +[] +let ``mapi works with row index and col index on rectangular matrix`` () = + let coo = + CoordinateList( + 3UL, + 5UL, + [ (1UL, 2UL, 15); (2UL, 2UL, 13) ] + ) + + let matrix = fromCoordinateList coo + + let result = + mapi matrix (fun (i: uint64) (j: uint64) (x: int option) -> + match x with + | Some v -> Some(v * int (j) + int (i)) + | None -> None) + + let actual = toCoordinateList result + + Assert.Equal( + CoordinateList( + 3UL, + 5UL, + [ (1UL, 2UL, 31); (2UL, 2UL, 28) ] + ), + actual + ) + +[] +let ``mapi on empty matrix returns empty matrix`` () = + let matrix = fromCoordinateList (CoordinateList(0UL, 0UL, [])) + + let result = + mapi matrix (fun (i: uint64) (j: uint64) (x: int option) -> + match x with + | Some v -> Some((v + int (1)) * 3 + 2 * int (j)) + | None -> None) + + let actual = toCoordinateList result + Assert.Equal(CoordinateList(0UL, 0UL, []), actual) + +[] +let ``mapi with special function returns empty matrix`` () = + let coo = + CoordinateList( + 5UL, + 7UL, + [ (1UL, 2UL, 15); (4UL, 5UL, 13) ] + ) + + let matrix = fromCoordinateList coo + + let result = + mapi matrix (fun (i: uint64) (j: uint64) (x: int option) -> + match x with + | Some v -> + match (int (i) + int (j)) % 2 with + | 0 -> Some(v * 2) + | _ -> None + | None -> None) + + let actual = toCoordinateList result + Assert.Equal(CoordinateList(5UL, 7UL, []), actual) + +[] +let ``mapi works with function does not depend on the indexes`` () = + let coo = + CoordinateList( + 5UL, + 7UL, + [ (1UL, 2UL, 15); (4UL, 5UL, 13) ] + ) + + let matrix = fromCoordinateList coo + + let result = + mapi matrix (fun (i: uint64) (j: uint64) (x: int option) -> + match x with + | Some v -> Some(v * 2) + | None -> None) + + let actual = toCoordinateList result + + Assert.Equal( + CoordinateList( + 5UL, + 7UL, + [ (1UL, 2UL, 30); (4UL, 5UL, 26) ] + ), + actual + ) + +[] +let ``slice returns error when row start is negative`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 6UL, + [ (2UL, 2UL, 3); (3UL, 4UL, 17) ] + ) + ) + + match slice matrix -1 4 2 3 with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("Start row should be >= 0", msg) + +[] +let ``slice returns error when row end is negative`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 6UL, + [ (2UL, 2UL, 3); (3UL, 4UL, 17) ] + ) + ) + + match slice matrix 1 -4 2 3 with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("End row should be >= 0", msg) + +[] +let ``slice returns error when col start is negative`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 6UL, + [ (2UL, 2UL, 3); (3UL, 4UL, 17) ] + ) + ) + + match slice matrix 1 4 -2 3 with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("Start column should be >= 0", msg) + +[] +let ``slice returns error when col end is negative`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 6UL, + [ (2UL, 2UL, 3); (3UL, 4UL, 17) ] + ) + ) + + match slice matrix 1 4 2 -3 with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("End column should be >= 0", msg) + +[] +let ``slice returns error when row start is out of range`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 6UL, + [ (2UL, 2UL, 3); (3UL, 4UL, 17) ] + ) + ) + + match slice matrix 6 4 2 3 with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("Start row is out of matrix length", msg) + +[] +let ``slice returns error when row end is out of range`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 6UL, + [ (2UL, 2UL, 3); (3UL, 4UL, 17) ] + ) + ) + + match slice matrix 1 10 2 3 with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("End row is out of matrix length", msg) + +[] +let ``slice returns error when col start is out of range`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 6UL, + [ (2UL, 2UL, 3); (3UL, 4UL, 17) ] + ) + ) + + match slice matrix 1 4 10 3 with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("Start column is out of matrix length", msg) + +[] +let ``slice returns error when col end is out of range`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 6UL, + [ (2UL, 2UL, 3); (3UL, 4UL, 17) ] + ) + ) + + match slice matrix 1 2 2 10 with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("End column is out of matrix length", msg) + +[] +let ``slice returns error when row end is less than row start`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 6UL, + [ (2UL, 2UL, 3); (3UL, 4UL, 17) ] + ) + ) + + match slice matrix 2 1 2 3 with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("Start row should be <= end row", msg) + +[] +let ``slice returns error when col end is less than col start`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 6UL, + [ (2UL, 2UL, 3); (3UL, 4UL, 17) ] + ) + ) + + match slice matrix 1 2 3 2 with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("Start column should be <= end column", msg) + +[] +let ``slice returns correct square submatrix`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 7UL, + 7UL, + [ (2UL, 2UL, 33); (5UL, 5UL, 28) ] + ) + ) + + match slice matrix 1 3 1 3 with + | Result.Ok result -> + let coo = toCoordinateList result + Assert.Equal(CoordinateList(3UL, 3UL, [ (1UL, 1UL, 33) ]), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns correct rectangular submatrix`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 5UL, + 9UL, + [ (3UL, 7UL, 33); (1UL, 2UL, 28) ] + ) + ) + + match slice matrix 2 4 5 8 with + | Result.Ok result -> + let coo = toCoordinateList result + Assert.Equal(CoordinateList(3UL, 4UL, [ (1UL, 2UL, 33) ]), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns empty matrix`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 7UL, + 9UL, + [ (3UL, 3UL, 33); (1UL, 2UL, 28) ] + ) + ) + + match slice matrix 4 6 5 8 with + | Result.Ok result -> + let coo = toCoordinateList result + Assert.Equal(CoordinateList(3UL, 4UL, []), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns single submatrix`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 7UL, + 9UL, + [ (3UL, 3UL, 33); (1UL, 2UL, 28) ] + ) + ) + + match slice matrix 1 1 2 2 with + | Result.Ok result -> + let coo = toCoordinateList result + Assert.Equal(CoordinateList(1UL, 1UL, [ (0UL, 0UL, 28) ]), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns correct submatrix equals to matrix`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 7UL, + 9UL, + [ (3UL, 3UL, 33); (1UL, 2UL, 28) ] + ) + ) + + match slice matrix 0 6 0 8 with + | Result.Ok result -> + let coo = toCoordinateList result + + Assert.Equal( + CoordinateList( + 7UL, + 9UL, + [ (1UL, 2UL, 28); (3UL, 3UL, 33) ] + ), + coo + ) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns correct submatrix when row start of submatrix equals to row start of matrix`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 7UL, + 7UL, + [ (0UL, 0UL, 10) + (3UL, 3UL, 33) + (6UL, 6UL, 6) ] + ) + ) + + match slice matrix 0 4 0 4 with + | Result.Ok result -> + let coo = toCoordinateList result + + Assert.Equal( + CoordinateList( + 5UL, + 5UL, + [ (0UL, 0UL, 10); (3UL, 3UL, 33) ] + ), + coo + ) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns correct submatrix when row end of submatrix equals to row end of matrix`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 7UL, + 7UL, + [ (0UL, 0UL, 10) + (3UL, 3UL, 33) + (6UL, 6UL, 6) ] + ) + ) + + match slice matrix 2 6 2 4 with + | Result.Ok result -> + let coo = toCoordinateList result + Assert.Equal(CoordinateList(5UL, 3UL, [ (1UL, 1UL, 33) ]), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns correct submatrix when col start of submatrix equals to col start of matrix`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 7UL, + 7UL, + [ (0UL, 0UL, 10) + (3UL, 3UL, 33) + (6UL, 6UL, 6) ] + ) + ) + + match slice matrix 2 5 0 6 with + | Result.Ok result -> + let coo = toCoordinateList result + Assert.Equal(CoordinateList(4UL, 7UL, [ (1UL, 3UL, 33) ]), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns correct submatrix when col end of submatrix equals to col end of matrix`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 7UL, + 7UL, + [ (0UL, 0UL, 10) + (3UL, 3UL, 33) + (6UL, 6UL, 6) ] + ) + ) + + match slice matrix 2 4 2 6 with + | Result.Ok result -> + let coo = toCoordinateList result + Assert.Equal(CoordinateList(3UL, 5UL, [ (1UL, 1UL, 33) ]), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns single column`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 10UL, + 10UL, + [ (1UL, 3UL, 1) + (2UL, 2UL, 2) + (5UL, 7UL, 3) ] + ) + ) + + match slice matrix 1 6 2 2 with + | Result.Ok result -> + let coo = toCoordinateList result + Assert.Equal(CoordinateList(6UL, 1UL, [ (1UL, 0UL, 2) ]), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns single row`` () = + let matrix = + fromCoordinateList ( + CoordinateList( + 10UL, + 10UL, + [ (1UL, 3UL, 1) + (2UL, 2UL, 2) + (5UL, 7UL, 3) ] + ) + ) + + match slice matrix 5 5 3 9 with + | Result.Ok result -> + let coo = toCoordinateList result + Assert.Equal(CoordinateList(1UL, 7UL, [ (0UL, 4UL, 3) ]), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``let reduceRows sum on square power of two matrix`` () = + let coo = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 12) + (0UL, 1UL, 15) + (1UL, 0UL, 17) + (1UL, 1UL, 3) ] + ) + + let matrix = fromCoordinateList coo + + let add x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a + b) + + let result = reduceRows add matrix + let vectorCoordinates = Vector.toCoordinateList result + + let expected = + Vector.CoordinateList(2UL, [ (0UL, 27); (1UL, 20) ]) + + Assert.Equal(expected, vectorCoordinates) + +[] +let ``let reduceRows sum on square power of two matrix with empty row`` () = + let coo = + CoordinateList( + 2UL, + 2UL, + [ (1UL, 0UL, 17); (1UL, 1UL, 3) ] + ) + + let matrix = fromCoordinateList coo + + let add x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a + b) + + let result = reduceRows add matrix + let vectorCoordinates = Vector.toCoordinateList result + + let expected = + Vector.CoordinateList(2UL, [ (1UL, 20) ]) + + Assert.Equal(expected, vectorCoordinates) + +[] +let ``let reduceRows sum on square not power of two matrix`` () = + let coo = + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 5) + (0UL, 1UL, 7) + (0UL, 2UL, 9) + (1UL, 1UL, 11) + (1UL, 2UL, 13) + (2UL, 0UL, 15) + (2UL, 1UL, 17) ] + ) + + let matrix = fromCoordinateList coo + + let add x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a + b) + + let result = reduceRows add matrix + let vectorCoordinates = Vector.toCoordinateList result + + let expected = + Vector.CoordinateList( + 3UL, + [ (0UL, 21); (1UL, 24); (2UL, 32) ] + ) + + Assert.Equal(expected, vectorCoordinates) + +[] +let ``let reduceRows mul on square not power of two matrix`` () = + let coo = + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 5) + (0UL, 1UL, 7) + (0UL, 2UL, 9) + (1UL, 1UL, 11) + (1UL, 2UL, 13) + (2UL, 0UL, 15) + (2UL, 1UL, 17) ] + ) + + let matrix = fromCoordinateList coo + + let mul x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a * b) + + let result = reduceRows mul matrix + let vectorCoordinates = Vector.toCoordinateList result + + let expected = + Vector.CoordinateList( + 3UL, + [ (0UL, 315); (1UL, 143); (2UL, 255) ] + ) + + Assert.Equal(expected, vectorCoordinates) + +[] +let ``let reduceRows sum on rectangular matrix`` () = + let coo = + CoordinateList( + 2UL, + 3UL, + [ (0UL, 0UL, 5) + (0UL, 1UL, 7) + (0UL, 2UL, 9) + (1UL, 1UL, 11) + (1UL, 2UL, 13) ] + ) + + let matrix = fromCoordinateList coo + + let sum x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a + b) + + let result = reduceRows sum matrix + let vectorCoordinates = Vector.toCoordinateList result + + let expected = + Vector.CoordinateList(2UL, [ (0UL, 21); (1UL, 24) ]) + + Assert.Equal(expected, vectorCoordinates) + +[] +let ``let reduceRows sum on empty matrix`` () = + let coo = CoordinateList(2UL, 3UL, []) + let matrix = fromCoordinateList coo + + let sum x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a + b) + + let result = reduceRows sum matrix + let vectorCoordinates = Vector.toCoordinateList result + let expected = Vector.CoordinateList(2UL, []) + Assert.Equal(expected, vectorCoordinates) + +[] +let ``let reduceRows mul on single matrix`` () = + let coo = + CoordinateList(1UL, 1UL, [ (0UL, 0UL, 33) ]) + + let matrix = fromCoordinateList coo + + let mul x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a * b) + + let result = reduceRows mul matrix + let vectorCoordinates = Vector.toCoordinateList result + + let expected = + Vector.CoordinateList(1UL, [ (0UL, 33) ]) + + Assert.Equal(expected, vectorCoordinates) + + +[] +let ``let reduceCols sum on square power of two matrix`` () = + let coo = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 12) + (0UL, 1UL, 15) + (1UL, 0UL, 17) + (1UL, 1UL, 3) ] + ) + + let matrix = fromCoordinateList coo + + let add x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a + b) + + let result = reduceCols add matrix + let vectorCoordinates = Vector.toCoordinateList result + + let expected = + Vector.CoordinateList(2UL, [ (0UL, 29); (1UL, 18) ]) + + Assert.Equal(expected, vectorCoordinates) + +[] +let ``let reduceCols sum on square power of two matrix with empty col`` () = + let coo = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 17); (1UL, 0UL, 3) ] + ) + + let matrix = fromCoordinateList coo + + let add x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a + b) + + let result = reduceCols add matrix + let vectorCoordinates = Vector.toCoordinateList result + + let expected = + Vector.CoordinateList(2UL, [ (0UL, 20) ]) + + Assert.Equal(expected, vectorCoordinates) + +[] +let ``let reduceCols sum on square not power of two matrix`` () = + let coo = + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 5) + (0UL, 1UL, 7) + (0UL, 2UL, 9) + (1UL, 1UL, 11) + (1UL, 2UL, 13) + (2UL, 0UL, 15) + (2UL, 1UL, 17) ] + ) + + let matrix = fromCoordinateList coo + + let add x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a + b) + + let result = reduceCols add matrix + let vectorCoordinates = Vector.toCoordinateList result + + let expected = + Vector.CoordinateList( + 3UL, + [ (0UL, 20); (1UL, 35); (2UL, 22) ] + ) + + Assert.Equal(expected, vectorCoordinates) + +[] +let ``let reduceCols mul on square not power of two matrix`` () = + let coo = + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 5) + (0UL, 1UL, 7) + (0UL, 2UL, 9) + (1UL, 1UL, 11) + (1UL, 2UL, 13) + (2UL, 0UL, 15) + (2UL, 1UL, 17) ] + ) + + let matrix = fromCoordinateList coo + + let mul x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a * b) + + let result = reduceCols mul matrix + let vectorCoordinates = Vector.toCoordinateList result + + let expected = + Vector.CoordinateList( + 3UL, + [ (0UL, 75); (1UL, 1309); (2UL, 117) ] + ) + + Assert.Equal(expected, vectorCoordinates) + +[] +let ``let reduceCols sum on rectangular matrix`` () = + let coo = + CoordinateList( + 2UL, + 3UL, + [ (0UL, 0UL, 5) + (0UL, 1UL, 7) + (0UL, 2UL, 9) + (1UL, 1UL, 11) + (1UL, 2UL, 13) ] + ) + + let matrix = fromCoordinateList coo + + let sum x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a + b) + + let result = reduceCols sum matrix + let vectorCoordinates = Vector.toCoordinateList result + + let expected = + Vector.CoordinateList( + 3UL, + [ (0UL, 5); (1UL, 18); (2UL, 22) ] + ) + + Assert.Equal(expected, vectorCoordinates) + +[] +let ``let reduceCols sum on empty matrix`` () = + let coo = CoordinateList(2UL, 3UL, []) + let matrix = fromCoordinateList coo + + let sum x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a + b) + + let result = reduceCols sum matrix + let vectorCoordinates = Vector.toCoordinateList result + let expected = Vector.CoordinateList(3UL, []) + Assert.Equal(expected, vectorCoordinates) + +[] +let ``let reduceCols mul on single matrix`` () = + let coo = + CoordinateList(1UL, 1UL, [ (0UL, 0UL, 33) ]) + + let matrix = fromCoordinateList coo + + let mul x y = + match x, y with + | None, None -> None + | None, Some(b) -> Some(b) + | Some(a), None -> Some(a) + | Some(a), Some(b) -> Some(a * b) + + let result = reduceCols mul matrix + let vectorCoordinates = Vector.toCoordinateList result + + let expected = + Vector.CoordinateList(1UL, [ (0UL, 33) ]) + + Assert.Equal(expected, vectorCoordinates) + +[] +let ``kronecker product with square power of 2 x square power of two matrixes`` () = + let cooA = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + + let A = fromCoordinateList cooA + + let cooB = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 5) + (0UL, 1UL, 6) + (1UL, 0UL, 7) + (1UL, 1UL, 8) ] + ) + + let B = fromCoordinateList cooB + + let multiplyOp a b = Some(a * b) + + match kroneckerProduct A B multiplyOp with + | Error msg -> Assert.True(false, msg) + | Ok result -> + let coo = toCoordinateList result + + let expected = + CoordinateList( + 4UL, + 4UL, + [ (0UL, 0UL, 5) + (0UL, 1UL, 6) + (0UL, 2UL, 10) + (0UL, 3UL, 12) + (1UL, 0UL, 7) + (1UL, 1UL, 8) + (1UL, 2UL, 14) + (1UL, 3UL, 16) + (2UL, 0UL, 15) + (2UL, 1UL, 18) + (2UL, 2UL, 20) + (2UL, 3UL, 24) + (3UL, 0UL, 21) + (3UL, 1UL, 24) + (3UL, 2UL, 28) + (3UL, 3UL, 32) ] + ) + + Assert.Equal(expected, coo) + +[] +let ``kronecker product with square not power of 2 x square not power of two matrixes`` () = + let cooA = + 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 A = fromCoordinateList cooA + + let cooB = + CoordinateList( + 3UL, + 3UL, + [ (0UL, 0UL, 10) + (0UL, 1UL, 11) + (0UL, 2UL, 12) + (1UL, 0UL, 13) + (1UL, 1UL, 14) + (1UL, 2UL, 15) + (2UL, 0UL, 16) + (2UL, 1UL, 17) + (2UL, 2UL, 18) ] + ) + + let B = fromCoordinateList cooB + + let multiplyOp a b = Some(a * b) + + match kroneckerProduct A B multiplyOp with + | Error msg -> Assert.True(false, msg) + | Ok result -> + let coo = toCoordinateList result + + let expected = + CoordinateList( + 9UL, + 9UL, + [ (0UL, 0UL, 10) + (0UL, 1UL, 11) + (0UL, 2UL, 12) + (0UL, 3UL, 20) + (0UL, 4UL, 22) + (0UL, 5UL, 24) + (0UL, 6UL, 30) + (0UL, 7UL, 33) + (0UL, 8UL, 36) + (1UL, 0UL, 13) + (1UL, 1UL, 14) + (1UL, 2UL, 15) + (1UL, 3UL, 26) + (1UL, 4UL, 28) + (1UL, 5UL, 30) + (1UL, 6UL, 39) + (1UL, 7UL, 42) + (1UL, 8UL, 45) + (2UL, 0UL, 16) + (2UL, 1UL, 17) + (2UL, 2UL, 18) + (2UL, 3UL, 32) + (2UL, 4UL, 34) + (2UL, 5UL, 36) + (2UL, 6UL, 48) + (2UL, 7UL, 51) + (2UL, 8UL, 54) + (3UL, 0UL, 40) + (3UL, 1UL, 44) + (3UL, 2UL, 48) + (3UL, 3UL, 50) + (3UL, 4UL, 55) + (3UL, 5UL, 60) + (3UL, 6UL, 60) + (3UL, 7UL, 66) + (3UL, 8UL, 72) + (4UL, 0UL, 52) + (4UL, 1UL, 56) + (4UL, 2UL, 60) + (4UL, 3UL, 65) + (4UL, 4UL, 70) + (4UL, 5UL, 75) + (4UL, 6UL, 78) + (4UL, 7UL, 84) + (4UL, 8UL, 90) + (5UL, 0UL, 64) + (5UL, 1UL, 68) + (5UL, 2UL, 72) + (5UL, 3UL, 80) + (5UL, 4UL, 85) + (5UL, 5UL, 90) + (5UL, 6UL, 96) + (5UL, 7UL, 102) + (5UL, 8UL, 108) + (6UL, 0UL, 70) + (6UL, 1UL, 77) + (6UL, 2UL, 84) + (6UL, 3UL, 80) + (6UL, 4UL, 88) + (6UL, 5UL, 96) + (6UL, 6UL, 90) + (6UL, 7UL, 99) + (6UL, 8UL, 108) + (7UL, 0UL, 91) + (7UL, 1UL, 98) + (7UL, 2UL, 105) + (7UL, 3UL, 104) + (7UL, 4UL, 112) + (7UL, 5UL, 120) + (7UL, 6UL, 117) + (7UL, 7UL, 126) + (7UL, 8UL, 135) + (8UL, 0UL, 112) + (8UL, 1UL, 119) + (8UL, 2UL, 126) + (8UL, 3UL, 128) + (8UL, 4UL, 136) + (8UL, 5UL, 144) + (8UL, 6UL, 144) + (8UL, 7UL, 153) + (8UL, 8UL, 162) ] + ) + + Assert.Equal(expected, coo) + +[] +let ``kronecker product with rectangular and square matrixes`` () = + let cooA = + CoordinateList( + 2UL, + 3UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 2UL, 4) ] + ) + + let A = fromCoordinateList cooA + + let cooB = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 5) + (0UL, 1UL, 6) + (1UL, 0UL, 7) + (1UL, 1UL, 8) ] + ) + + let B = fromCoordinateList cooB + + let result = kroneckerProduct A B (fun a b -> Some(a * b)) + + match result with + | Error msg -> Assert.True(false, msg) + | Ok res -> + let coo = toCoordinateList res + + let expectedElements = + [ (0UL, 0UL, 5) + (0UL, 1UL, 6) + (1UL, 0UL, 7) + (1UL, 1UL, 8) + + (0UL, 2UL, 10) + (0UL, 3UL, 12) + (1UL, 2UL, 14) + (1UL, 3UL, 16) + + (2UL, 0UL, 15) + (2UL, 1UL, 18) + (3UL, 0UL, 21) + (3UL, 1UL, 24) + + (2UL, 4UL, 20) + (2UL, 5UL, 24) + (3UL, 4UL, 28) + (3UL, 5UL, 32) ] + |> List.sortBy (fun (r, c, _) -> (r, c)) + + let actualElements = coo.list |> List.sortBy (fun (r, c, _) -> (r, c)) + + Assert.Equal(4UL, coo.nrows) + Assert.Equal(6UL, coo.ncols) + Assert.Equal * uint64 * int>>(expectedElements, actualElements) + +[] +let ``kronecker product with square and rectangular matrixes`` () = + let cooA = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + + let A = fromCoordinateList cooA + + let cooB = + CoordinateList( + 2UL, + 3UL, + [ (0UL, 0UL, 5) + (0UL, 1UL, 6) + (0UL, 2UL, 7) + (1UL, 0UL, 8) + (1UL, 1UL, 9) + (1UL, 2UL, 10) ] + ) + + let B = fromCoordinateList cooB + + let result = kroneckerProduct A B (fun a b -> Some(a * b)) + + match result with + | Error msg -> Assert.True(false, msg) + | Ok res -> + let coo = toCoordinateList res + + let expectedElements = + [ (0UL, 0UL, 5) + (0UL, 1UL, 6) + (0UL, 2UL, 7) + (1UL, 0UL, 8) + (1UL, 1UL, 9) + (1UL, 2UL, 10) + + (0UL, 3UL, 10) + (0UL, 4UL, 12) + (0UL, 5UL, 14) + (1UL, 3UL, 16) + (1UL, 4UL, 18) + (1UL, 5UL, 20) + + (2UL, 0UL, 15) + (2UL, 1UL, 18) + (2UL, 2UL, 21) + (3UL, 0UL, 24) + (3UL, 1UL, 27) + (3UL, 2UL, 30) + + (2UL, 3UL, 20) + (2UL, 4UL, 24) + (2UL, 5UL, 28) + (3UL, 3UL, 32) + (3UL, 4UL, 36) + (3UL, 5UL, 40) ] + |> List.sortBy (fun (r, c, _) -> (r, c)) + + let actualElements = coo.list |> List.sortBy (fun (r, c, _) -> (r, c)) + + Assert.Equal(4UL, coo.nrows) + Assert.Equal(6UL, coo.ncols) + Assert.Equal * uint64 * int>>(expectedElements, actualElements) + +[] +let ``kronecker product of matrix with empty matrix`` () = + let cooA = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + + let A = fromCoordinateList cooA + + let emptyMatrix = fromCoordinateList (CoordinateList(2UL, 2UL, [])) + + let result = kroneckerProduct A emptyMatrix (fun a b -> Some(a * b)) + + match result with + | Error msg -> Assert.True(false, msg) + | Ok res -> + let coo = toCoordinateList res + let expected = CoordinateList(4UL, 4UL, []) + Assert.Equal(expected, coo) + +[] +let ``kronecker product of empty matrix with matrix`` () = + let emptyMatrix = fromCoordinateList (CoordinateList(2UL, 2UL, [])) + + let cooB = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + + let B = fromCoordinateList cooB + + let result = kroneckerProduct emptyMatrix B (fun a b -> Some(a * b)) + + match result with + | Error msg -> Assert.True(false, msg) + | Ok res -> + let coo = toCoordinateList res + let expected = CoordinateList(4UL, 4UL, []) + Assert.Equal(expected, coo) + +[] +let ``kronecker product of matrix with zeros`` () = + let cooA = + CoordinateList(1UL, 1UL, [ (0UL, 0UL, 2) ]) + + let A = fromCoordinateList cooA + + let cooB = + CoordinateList(2UL, 2UL, [ (0UL, 0UL, 0); (1UL, 1UL, 3) ]) + + let B = fromCoordinateList cooB + + let result = kroneckerProduct A B (fun a b -> Some(a * b)) + + match result with + | Error msg -> Assert.True(false, msg) + | Ok res -> + let coo = toCoordinateList res + + let expectedElements = + [ (0UL, 0UL, 0); (1UL, 1UL, 6) ] + |> List.sortBy (fun (r, c, _) -> (r, c)) + + let actualElements = coo.list |> List.sortBy (fun (r, c, _) -> (r, c)) + + Assert.Equal(2UL, coo.nrows) + Assert.Equal(2UL, coo.ncols) + Assert.Equal * uint64 * int>>(expectedElements, actualElements) + +[] +let ``kronecker product resulting entirely in explicit zeros`` () = + let cooA = + CoordinateList(2UL, 2UL, [ (0UL, 0UL, 0); (1UL, 1UL, 0) ]) + + let A = fromCoordinateList cooA + + let cooB = + CoordinateList(1UL, 1UL, [ (0UL, 0UL, 5) ]) + + let B = fromCoordinateList cooB + + let result = kroneckerProduct A B (fun a b -> Some(a * b)) + + match result with + | Error msg -> Assert.True(false, msg) + | Ok res -> + let coo = toCoordinateList res + + let expectedElements = + [ (0UL, 0UL, 0); (1UL, 1UL, 0) ] + |> List.sortBy (fun (r, c, _) -> (r, c)) + + let actualElements = coo.list |> List.sortBy (fun (r, c, _) -> (r, c)) + + Assert.Equal(2UL, coo.nrows) + Assert.Equal(2UL, coo.ncols) + Assert.Equal * uint64 * int>>(expectedElements, actualElements) + +[] +let ``kronecker product of square matrix with matrix 1x1`` () = + let cooA = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + + let A = fromCoordinateList cooA + + let cooB = + CoordinateList(1UL, 1UL, [ (0UL, 0UL, 3) ]) + + let B = fromCoordinateList cooB + + let result = kroneckerProduct A B (fun a b -> Some(a * b)) + + match result with + | Error msg -> Assert.True(false, msg) + | Ok res -> + let coo = toCoordinateList res + + let expected = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 3) + (0UL, 1UL, 6) + (1UL, 0UL, 9) + (1UL, 1UL, 12) ] + ) + + Assert.Equal(expected, coo) + +[] +let ``kronecker product of matrix 1x1 with square matrix`` () = + let cooA = + CoordinateList(1UL, 1UL, [ (0UL, 0UL, 3) ]) + + let A = fromCoordinateList cooA + + let cooB = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + + let B = fromCoordinateList cooB + + let result = kroneckerProduct A B (fun a b -> Some(a * b)) + + match result with + | Error msg -> Assert.True(false, msg) + | Ok res -> + let coo = toCoordinateList res + + let expected = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 3) + (0UL, 1UL, 6) + (1UL, 0UL, 9) + (1UL, 1UL, 12) ] + ) + + Assert.Equal(expected, coo) + +[] +let ``kronecker dimension check`` () = + let cooA = + CoordinateList(3UL, 4UL, [ (0UL, 0UL, 1) ]) + + let A = fromCoordinateList cooA + + let cooB = + CoordinateList(2UL, 5UL, [ (0UL, 0UL, 1) ]) + + let B = fromCoordinateList cooB + + let result = kroneckerProduct A B (fun a b -> Some(a * b)) + + match result with + | Error msg -> Assert.True(false, msg) + | Ok res -> + Assert.Equal(6UL, res.nrows) + Assert.Equal(20UL, res.ncols) + +[] +let ``kronecker product with sparse matrix on dense matrix`` () = + let cooA = + CoordinateList(10UL, 10UL, [ (5UL, 5UL, 2) ]) + + let A = fromCoordinateList cooA + + let cooB = + 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 B = fromCoordinateList cooB + + let result = kroneckerProduct A B (fun a b -> Some(a * b)) + + match result with + | Error msg -> Assert.True(false, msg) + | Ok res -> + let coo = toCoordinateList res + + let expected = + CoordinateList( + 30UL, + 30UL, + [ (15UL, 15UL, 2) + (15UL, 16UL, 4) + (15UL, 17UL, 6) + (16UL, 15UL, 8) + (16UL, 16UL, 10) + (16UL, 17UL, 12) + (17UL, 15UL, 14) + (17UL, 16UL, 16) + (17UL, 17UL, 18) ] + ) + + Assert.Equal(expected, coo) + +[] +let ``kronecker product with filtering (only even results)`` () = + let cooA = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + + let A = fromCoordinateList cooA + + let cooB = + CoordinateList( + 2UL, + 2UL, + [ (0UL, 0UL, 1) + (0UL, 1UL, 2) + (1UL, 0UL, 3) + (1UL, 1UL, 4) ] + ) + + let B = fromCoordinateList cooB + + let evenOnly a b = + let prod = a * b + if prod % 2 = 0 then Some prod else None + + let result = kroneckerProduct A B evenOnly + + match result with + | Error msg -> Assert.True(false, msg) + | Ok res -> + let coo = toCoordinateList res + Assert.True(coo.list |> List.forall (fun (_, _, v) -> v % 2 = 0)) + Assert.True(List.length coo.list < 16) diff --git a/QuadTree.Tests/Tests.Vector.fs b/QuadTree.Tests/Tests.Vector.fs index 4eb1af9..61ee41f 100644 --- a/QuadTree.Tests/Tests.Vector.fs +++ b/QuadTree.Tests/Tests.Vector.fs @@ -1,6 +1,7 @@ module Vector.Tests open Xunit +open System open Vector open Common @@ -903,3 +904,252 @@ let ``Init vector`` () = let actual = Vector.init 3UL (fun i -> Some(int i)) Assert.Equal(expected, actual) + + +[] +let ``map on empty vector returns empty vector`` () = + let vec = fromCoordinateList (CoordinateList(0UL, [])) + + let result = + map vec (fun (x: int option) -> + match x with + | Some v -> Some(v * 3) + | None -> None) + + let coo = toCoordinateList result + Assert.Equal(CoordinateList(0UL, []), coo) + +[] +let ``map with function that turns all the elements into zeros`` () = + let vec = + fromCoordinateList (CoordinateList(7UL, [ (1UL, 3); (2UL, 3); (5UL, 100) ])) + + let result = map vec (fun _ -> Some 0) + let coo = toCoordinateList result + + Assert.Equal( + CoordinateList( + 7UL, + [ (0UL, 0) + (1UL, 0) + (2UL, 0) + (3UL, 0) + (4UL, 0) + (5UL, 0) + (6UL, 0) ] + ), + coo + ) + +[] +let ``map with function that resets all the elements`` () = + let vec = + fromCoordinateList (CoordinateList(7UL, [ (1UL, 3); (2UL, 3); (5UL, 100) ])) + + let result = map vec (fun _ -> None) + let coo = toCoordinateList result + Assert.Equal(CoordinateList(7UL, []), coo) + +[] +let ``map can change type from int to string`` () = + let vec = + fromCoordinateList (CoordinateList(3UL, [ (1UL, 11); (2UL, 33) ])) + + let result = + map vec (fun (x: int option) -> + match x with + | Some v -> Some(sprintf "str %d" v) + | None -> None) + + let coo = toCoordinateList result + Assert.Equal(CoordinateList(3UL, [ (1UL, "str 11"); (2UL, "str 33") ]), coo) + +[] +let ``fromCoordinateList with out-of-range index throws exception`` () = + let coo = CoordinateList(6UL, [ (9UL, 8) ]) + Assert.Throws(fun () -> fromCoordinateList coo |> ignore) + +[] +let ``fromCoordinateList with unsorted coordinates works correctly`` () = + let coo = + CoordinateList(7UL, [ (5UL, 3); (3UL, 2); (1UL, 100) ]) + + let vec = fromCoordinateList coo + let result = toCoordinateList vec + Assert.Equal(CoordinateList(7UL, [ (1UL, 100); (3UL, 2); (5UL, 3) ]), result) + +[] +let ``fromCoordinateList with duplicate indicies returns the last of them`` () = + let coo = CoordinateList(3UL, [ (1UL, 33); (1UL, 100) ]) + let vec = Vector.fromCoordinateList coo + let result = Vector.toCoordinateList vec + Assert.Equal(CoordinateList(3UL, [ (1UL, 100) ]), result) + +[] +let ``fromCoordinateList with zero size throws Exception`` () = + let coo = CoordinateList(0UL, [ (33UL, 33); (39UL, 1) ]) + Assert.Throws(fun () -> fromCoordinateList coo |> ignore) + +[] +let ``mapi works with index`` () = + let vec = + fromCoordinateList (CoordinateList(5UL, [ (2UL, 10); (3UL, 23) ])) + + let result = + mapi vec (fun (i: uint64) (x: int option) -> + match x with + | Some v -> Some(int i + v) + | None -> None) + + let coo = toCoordinateList result + Assert.Equal(CoordinateList(5UL, [ (2UL, 12); (3UL, 26) ]), coo) + +[] +let ``mapi on empty vector returns empty vector`` () = + let vec = fromCoordinateList (CoordinateList(0UL, [])) + + let result = + mapi vec (fun (i: uint64) (x: int option) -> + match x with + | Some v -> Some(int i + v) + | None -> None) + + let coo = toCoordinateList result + Assert.Equal(CoordinateList(0UL, []), coo) + +[] +let ``mapi with special function returns empty vector`` () = + let vec = + fromCoordinateList (CoordinateList(5UL, [ (1UL, 33); (3UL, 88) ])) + + let result = + mapi vec (fun (i: uint64) (x: int option) -> + match x with + | Some v -> + match int i % 2 with + | 0 -> Some(v * 2) + | _ -> None + | None -> None) + + let coo = toCoordinateList result + Assert.Equal(CoordinateList(5UL, []), coo) + +[] +let ``mapi works with function does not depend on the index`` () = + let vec = + fromCoordinateList (CoordinateList(5UL, [ (2UL, 10); (3UL, 23) ])) + + let result = + mapi vec (fun (i: uint64) (x: int option) -> + match x with + | Some v -> Some(v + 4) + | None -> None) + + let coo = toCoordinateList result + Assert.Equal(CoordinateList(5UL, [ (2UL, 14); (3UL, 27) ]), coo) + +[] +let ``slice returns error when start is negative`` () = + let vec = fromCoordinateList (CoordinateList(5UL, [ (2UL, 10) ])) + + match slice -1 3 vec with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("Start should be >= 0", msg) + +[] +let ``slice returns error when end is negative`` () = + let vec = fromCoordinateList (CoordinateList(5UL, [ (2UL, 10) ])) + + match slice 1 -3 vec with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("End should be >= 0", msg) + +[] +let ``slice returns error when start is out of range`` () = + let vec = fromCoordinateList (CoordinateList(5UL, [ (2UL, 10) ])) + + match slice 7 3 vec with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("Start is out of Vector length", msg) + +[] +let ``slice returns error when end is out of range`` () = + let vec = fromCoordinateList (CoordinateList(5UL, [ (2UL, 10) ])) + + match slice 3 7 vec with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("End is out of Vector length", msg) + +[] +let ``slice returns error when end is less than start`` () = + let vec = fromCoordinateList (CoordinateList(5UL, [ (2UL, 10) ])) + + match slice 4 3 vec with + | Result.Ok _ -> Assert.Fail("Expected Error") + | Result.Error msg -> Assert.Equal("End should be >= Start", msg) + +[] +let ``slice returns correct subvector`` () = + let vec = + fromCoordinateList (CoordinateList(7UL, [ (2UL, 10); (6UL, 20) ])) + + match slice 1 3 vec with + | Result.Ok result -> + let coo = Vector.toCoordinateList result + Assert.Equal(CoordinateList(3UL, [ (1UL, 10) ]), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns subvector without elements`` () = + let vec = + fromCoordinateList (CoordinateList(7UL, [ (5UL, 10); (6UL, 20) ])) + + match slice 0 2 vec with + | Result.Ok result -> + let coo = toCoordinateList result + Assert.Equal(CoordinateList(3UL, []), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns single subvector`` () = + let vec = + fromCoordinateList (CoordinateList(7UL, [ (2UL, 10); (6UL, 20) ])) + + match slice 2 2 vec with + | Result.Ok result -> + let coo = toCoordinateList result + Assert.Equal(CoordinateList(1UL, [ (0UL, 10) ]), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns correct subvector equals to vector`` () = + let vec = + fromCoordinateList (CoordinateList(7UL, [ (2UL, 10); (3UL, 33); (6UL, 20) ])) + + match slice 0 6 vec with + | Result.Ok result -> + let coo = toCoordinateList result + Assert.Equal(CoordinateList(7UL, [ (2UL, 10); (3UL, 33); (6UL, 20) ]), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns correct subvector when start of subvector equals to start of vector`` () = + let vec = + fromCoordinateList (CoordinateList(7UL, [ (0UL, 10); (3UL, 33); (6UL, 20) ])) + + match slice 0 4 vec with + | Result.Ok result -> + let coo = Vector.toCoordinateList result + Assert.Equal(CoordinateList(5UL, [ (0UL, 10); (3UL, 33) ]), coo) + | Result.Error msg -> Assert.Fail(msg) + +[] +let ``slice returns correct subvector when end of subvector equals to end of vector`` () = + let vec = + fromCoordinateList (CoordinateList(7UL, [ (0UL, 10); (3UL, 33); (6UL, 20) ])) + + match slice 2 6 vec with + | Result.Ok result -> + let coo = Vector.toCoordinateList result + Assert.Equal(CoordinateList(5UL, [ (1UL, 33); (4UL, 20) ]), coo) + | Result.Error msg -> Assert.Fail(msg) diff --git a/QuadTree/Matrix.fs b/QuadTree/Matrix.fs index 68ea7d7..e42917d 100644 --- a/QuadTree/Matrix.fs +++ b/QuadTree/Matrix.fs @@ -74,7 +74,20 @@ let private getQuadrantCoords (pr, pc) halfSize = (pr + halfSize * 1UL, pc + halfSize * 1UL) // SOUTH EAST let fromCoordinateList (coo: CoordinateList<'a>) = - let nvals = (uint64 <| List.length coo.list) * 1UL + let unique = + coo.list + |> List.groupBy (fun (i, j, _) -> (i, j)) + |> List.map (fun ((i, j), entries) -> + let value = entries |> List.map (fun (_, _, v) -> v) |> List.last + (i, j, value)) + + if + unique + |> List.exists (fun (i, j, _) -> uint64 i >= uint64 coo.nrows || uint64 j >= uint64 coo.ncols) + then + failwith "Coordinates out of range" + + let nvals = (uint64 <| List.length unique) * 1UL let nrows = coo.nrows let ncols = coo.ncols @@ -108,7 +121,7 @@ let fromCoordinateList (coo: CoordinateList<'a>) = (traverse swCoo swp halfSize) (traverse seCoo sep halfSize) - let tree = traverse coo.list (0UL, 0UL) storageSize + let tree = traverse unique (0UL, 0UL) storageSize SparseMatrix(nrows, ncols, nvals, Storage(storageSize * 1UL, tree)) @@ -135,11 +148,35 @@ let toCoordinateList (matrix: SparseMatrix<'a>) = let coo = traverse matrix.storage.data (0UL, 0UL) (uint64 matrix.storage.size) - CoordinateList(nrows, ncols, coo) + let sorted = List.sort coo + CoordinateList(nrows, ncols, sorted) let empty nrows ncols = fromCoordinateList (CoordinateList(nrows, ncols, [])) +let map (matrix: SparseMatrix<'a>) f = + let rec inner (size: uint64) (tree: qtree>) = + match tree with + | Node(nw, ne, sw, se) -> + let nwTree, nwNvals = inner (size / 2UL) nw + let neTree, neNvals = inner (size / 2UL) ne + let swTree, swNvals = inner (size / 2UL) sw + let seTree, seNvals = inner (size / 2UL) se + (mkNode nwTree neTree swTree seTree), nwNvals + neNvals + swNvals + seNvals + | 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 + + let newTree, newNvals = inner matrix.storage.size matrix.storage.data + SparseMatrix(matrix.nrows, matrix.ncols, newNvals, Storage(matrix.storage.size, newTree)) + let map2 (matrix1: SparseMatrix<_>) (matrix2: SparseMatrix<_>) f = let rec inner (size: uint64) matrix1 matrix2 = let _do x1 x2 x3 x4 y1 y2 y3 y4 = @@ -394,3 +431,340 @@ 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 slice + (matrix: SparseMatrix<'a>) + (rowStart: int) + (rowEnd: int) + (colStart: int) + (colEnd: int) + : Result, string> = + if rowStart < 0 then + Error "Start row should be >= 0" + elif rowEnd < 0 then + Error "End row should be >= 0" + elif colStart < 0 then + Error "Start column should be >= 0" + elif colEnd < 0 then + Error "End column should be >= 0" + elif rowStart > int matrix.nrows - 1 then + Error "Start row is out of matrix length" + elif rowEnd > int matrix.nrows - 1 then + Error "End row is out of matrix length" + elif colStart > int matrix.ncols - 1 then + Error "Start column is out of matrix length" + elif colEnd > int matrix.ncols - 1 then + Error "End column is out of matrix length" + elif rowStart > rowEnd then + Error "Start row should be <= end row" + elif colStart > colEnd then + Error "Start column should be <= end column" + else + let rowStartIdx = uint64 rowStart * 1UL + let rowEndIdx = uint64 rowEnd * 1UL + let colStartIdx = uint64 colStart * 1UL + let colEndIdx = uint64 colEnd * 1UL + let newRows = uint64 (rowEnd - rowStart + 1) * 1UL + let newCols = uint64 (colEnd - colStart + 1) * 1UL + + let newSize = + getNearestUpperPowerOfTwo (max (uint64 newRows) (uint64 newCols)) + * 1UL + + let rec cut (size: uint64) (row: uint64) (col: uint64) tree = + let sizeRow = (uint64 size) * 1UL + let sizeCol = (uint64 size) * 1UL + + match tree with + | Node(nw, ne, sw, se) -> + let half = size / 2UL + let halfRow = (uint64 half) * 1UL + let halfCol = (uint64 half) * 1UL + + mkNode + (cut half row col nw) + (cut half row (col + halfCol) ne) + (cut half (row + halfRow) col sw) + (cut half (row + halfRow) (col + halfCol) se) + | Leaf(Dummy) -> Leaf Dummy + | Leaf(UserValue(v)) when + row >= rowStartIdx + && row + sizeRow - 1UL <= rowEndIdx + && col >= colStartIdx + && col + sizeCol - 1UL <= colEndIdx + -> + Leaf(UserValue(v)) + | _ -> Leaf Dummy + + let cutTree = + cut matrix.storage.size 0UL 0UL matrix.storage.data + + let rec empty (size: uint64) = + match size with + | 1UL -> Leaf Dummy + | _ -> + let half = size / 2UL + let subtree = empty half + Node(subtree, subtree, subtree, subtree) + + let rec insert (size: uint64) (row: uint64) (col: uint64) value tree = + match size with + | 1UL -> Leaf(UserValue(value)) + | _ -> + let half = size / 2UL + let halfRow = (uint64 half) * 1UL + let halfCol = (uint64 half) * 1UL + + match tree with + | Node(nw, ne, sw, se) -> + if row < halfRow then + if col < halfCol then + Node(insert half row col value nw, ne, sw, se) + else + Node(nw, insert half row (col - halfCol) value ne, sw, se) + else if col < halfCol then + Node(nw, ne, insert half (row - halfRow) col value sw, se) + else + Node(nw, ne, sw, insert half (row - halfRow) (col - halfCol) value se) + | _ -> + let emptySub = empty half + + if row < halfRow then + if col < halfCol then + Node(insert half row col value emptySub, emptySub, emptySub, emptySub) + else + Node(emptySub, insert half row (col - halfCol) value emptySub, emptySub, emptySub) + else if col < halfCol then + Node(emptySub, emptySub, insert half (row - halfRow) col value emptySub, emptySub) + else + Node(emptySub, emptySub, emptySub, insert half (row - halfRow) (col - halfCol) value emptySub) + + let rec rebuild (size: uint64) (row: uint64) (col: uint64) tree acc = + match tree with + | Leaf(Dummy) -> acc + | Leaf(UserValue(v)) -> + let newRow = row - uint64 rowStart * 1UL + let newCol = col - uint64 colStart * 1UL + insert newSize newRow newCol v acc + | Node(nw, ne, sw, se) -> + let half = size / 2UL + let halfRow = (uint64 half) * 1UL + let halfCol = (uint64 half) * 1UL + let acc = rebuild half row col nw acc + let acc = rebuild half row (col + halfCol) ne acc + let acc = rebuild half (row + halfRow) col sw acc + rebuild half (row + halfRow) (col + halfCol) se acc + + let emptyTree = empty newSize + + let shiftedTree = + rebuild matrix.storage.size 0UL 0UL cutTree emptyTree + + let rec count (size: uint64) tree = + match tree with + | Node(nw, ne, sw, se) -> + let half = size / 2UL + count half nw + count half ne + count half sw + count half se + | Leaf(UserValue(_)) -> 1UL + | _ -> 0UL + + let nvals = count newSize shiftedTree + + Ok(SparseMatrix(newRows, newCols, nvals, Storage(newSize, shiftedTree))) + +let reduceRows (op: 'a option -> 'a option -> 'a option) (matrix: SparseMatrix<'a>) : Vector.SparseVector<'a> = + let rows = matrix.nrows + + let rec inner (size: uint64) (row: uint64) (col: uint64) tree acc = + match tree with + | Node(nw, ne, sw, se) -> + let half = size / 2UL + let halfRow = (uint64 half) * 1UL + let halfCol = (uint64 half) * 1UL + let acc = inner half row col nw acc + let acc = inner half row (col + halfCol) ne acc + let acc = inner half (row + halfRow) col sw acc + let acc = inner half (row + halfRow) (col + halfCol) se acc + acc + | Leaf(Dummy) -> acc + | Leaf(UserValue(None)) -> acc + | Leaf(UserValue(Some v)) -> (row, v) :: acc + + let pairs = + inner matrix.storage.size 0UL 0UL matrix.storage.data [] + + let grouped = + List.sortBy fst pairs + |> List.groupBy fst + |> List.map (fun (row, rowSet) -> row, rowSet |> List.map snd |> List.map Some) + + let reduced = + grouped + |> List.map (fun (row, values) -> + match values with + | [] -> row, None + | head :: tail -> row, List.fold op head tail) + + let data = + reduced + |> List.choose (fun (row, v) -> + match v with + | None -> None + | Some x -> Some(row, 0UL, x)) + |> List.sort + + let vectorData = + data |> List.map (fun (row, _, v) -> (uint64 row * 1UL, v)) + + Vector.fromCoordinateList (Vector.CoordinateList(uint64 rows * 1UL, vectorData)) + +let reduceCols (op: 'a option -> 'a option -> 'a option) (matrix: SparseMatrix<'a>) : Vector.SparseVector<'a> = + let cols = matrix.ncols + + let rec inner (size: uint64) (row: uint64) (col: uint64) tree acc = + match tree with + | Node(nw, ne, sw, se) -> + let half = size / 2UL + let halfRow = (uint64 half) * 1UL + let halfCol = (uint64 half) * 1UL + let acc = inner half row col nw acc + let acc = inner half row (col + halfCol) ne acc + let acc = inner half (row + halfRow) col sw acc + let acc = inner half (row + halfRow) (col + halfCol) se acc + acc + | Leaf(Dummy) -> acc + | Leaf(UserValue(None)) -> acc + | Leaf(UserValue(Some v)) -> (col, v) :: acc + + let pairs = + inner matrix.storage.size 0UL 0UL matrix.storage.data [] + + let grouped = + List.sortBy fst pairs + |> List.groupBy fst + |> List.map (fun (col, colSet) -> col, colSet |> List.map snd |> List.map Some) + + let reduced = + grouped + |> List.map (fun (col, values) -> + match values with + | [] -> col, None + | head :: tail -> col, List.fold op head tail) + + let data = + reduced + |> List.choose (fun (col, v) -> + match v with + | None -> None + | Some x -> Some(0UL, col, x)) + |> List.sort + + let vectorData = + data |> List.map (fun (_, col, v) -> (uint64 col * 1UL, v)) + + Vector.fromCoordinateList (Vector.CoordinateList(uint64 cols * 1UL, vectorData)) + +let kroneckerProduct + (matrix1: SparseMatrix<'a>) + (matrix2: SparseMatrix<'b>) + (f: 'a -> 'b -> 'c option) + : Result, string> = + let newRows = uint64 matrix1.nrows * uint64 matrix2.nrows * 1UL + let newCols = uint64 matrix1.ncols * uint64 matrix2.ncols * 1UL + + let newSize = + getNearestUpperPowerOfTwo (max (uint64 newRows) (uint64 newCols)) + * 1UL + + let rec empty (size: uint64) = + match size with + | 1UL -> Leaf Dummy + | _ -> + let half = size / 2UL + let subtree = empty half + Node(subtree, subtree, subtree, subtree) + + let rec insert (size: uint64) (row: uint64) (col: uint64) value tree = + match size with + | 1UL -> Leaf(UserValue(value)) + | _ -> + let half = size / 2UL + let halfRow = (uint64 half) * 1UL + let halfCol = (uint64 half) * 1UL + + match tree with + | Node(nw, ne, sw, se) -> + match (row < halfRow, col < halfCol) with + | true, true -> Node(insert half row col value nw, ne, sw, se) + | true, false -> Node(nw, insert half row (col - halfCol) value ne, sw, se) + | false, true -> Node(nw, ne, insert half (row - halfRow) col value sw, se) + | false, false -> Node(nw, ne, sw, insert half (row - halfRow) (col - halfCol) value se) + | _ -> + let emptySub = empty half + + match (row < halfRow, col < halfCol) with + | true, true -> Node(insert half row col value emptySub, emptySub, emptySub, emptySub) + | true, false -> Node(emptySub, insert half row (col - halfCol) value emptySub, emptySub, emptySub) + | false, true -> Node(emptySub, emptySub, insert half (row - halfRow) col value emptySub, emptySub) + | false, false -> + Node(emptySub, emptySub, emptySub, insert half (row - halfRow) (col - halfCol) value emptySub) + + let foldQuadtree folder state size tree = + let rec inner rowOffset colOffset currentSize subTree acc = + match subTree with + | Leaf Dummy -> acc + | Leaf(UserValue None) -> acc + | Leaf(UserValue(Some value)) -> + let rec loop currRow currCol currentAcc = + if currRow = rowOffset + currentSize then + currentAcc + elif currCol = colOffset + currentSize then + loop (currRow + 1UL) colOffset currentAcc + else + loop currRow (currCol + 1UL) (folder currentAcc currRow currCol value) + + loop rowOffset colOffset acc + | Node(nw, ne, sw, se) -> + let half = currentSize / 2UL + let acc1 = inner rowOffset colOffset half nw acc + let acc2 = inner rowOffset (colOffset + half) half ne acc1 + let acc3 = inner (rowOffset + half) colOffset half sw acc2 + inner (rowOffset + half) (colOffset + half) half se acc3 + + inner 0UL 0UL (uint64 size) tree state + + let mat2Rows = uint64 matrix2.nrows + let mat2Cols = uint64 matrix2.ncols + let initialTree = empty newSize + + let foldMatrixB acc rowMat1 colMat1 valMat1 = + foldQuadtree + (fun currentAcc rowMat2 colMat2 valMat2 -> + match f valMat1 valMat2 with + | Some computedVal -> + let destRow = rowMat1 * mat2Rows + rowMat2 + let destCol = colMat1 * mat2Cols + colMat2 + insert newSize (destRow * 1UL) (destCol * 1UL) (Some computedVal) currentAcc + | None -> currentAcc) + acc + matrix2.storage.size + matrix2.storage.data + + let finalTree = + foldQuadtree + (fun acc rowMat1 colMat1 valMat1 -> foldMatrixB acc rowMat1 colMat1 valMat1) + initialTree + matrix1.storage.size + matrix1.storage.data + + let rec count (size: uint64) tree = + match tree with + | Node(nw, ne, sw, se) -> + let half = size / 2UL + count half nw + count half ne + count half sw + count half se + | Leaf(UserValue(Some _)) -> 1UL + | _ -> 0UL + + let nvals = count newSize finalTree + + Ok(SparseMatrix(newRows, newCols, nvals, Storage(newSize, finalTree))) diff --git a/QuadTree/Vector.fs b/QuadTree/Vector.fs index b7bcb44..42961f2 100644 --- a/QuadTree/Vector.fs +++ b/QuadTree/Vector.fs @@ -307,6 +307,16 @@ let update (vector: SparseVector<_>) i v op = Error Error.InconsistentSizeOfArguments let fromCoordinateList (lst: CoordinateList<'a>) : SparseVector<'a> = + let unique = + lst.data + |> List.groupBy fst + |> List.map (fun (idx, entries) -> + let value = entries |> List.map snd |> List.last + (idx, value)) + + if unique |> List.exists (fun (idx, _) -> uint64 idx >= uint64 lst.length) then + failwith "Index out of range" + let length = lst.length let nvals = (uint64 <| List.length lst.data) * 1UL let storageSize = (getNearestUpperPowerOfTwo <| uint64 length) * 1UL @@ -325,7 +335,7 @@ let fromCoordinateList (lst: CoordinateList<'a>) : SparseVector<'a> = mkNode left right, rCoordinates - let sortedCoordinates = List.sort lst.data + let sortedCoordinates = List.sort unique let tree, _ = traverse sortedCoordinates 0UL ((uint64 storageSize) * 1UL) @@ -561,3 +571,91 @@ let scatter | Error x -> Error x) (Ok w) | Error x -> Error Error.InconsistentStructureOfStorages + + +let slice (_start: int) (_end: int) (vector: SparseVector<'a>) : Result, string> = + if _start < 0 then + Error "Start should be >= 0" + elif _end < 0 then + Error "End should be >= 0" + elif _start > int vector.length - 1 then + Error "Start is out of Vector length" + elif _end > int vector.length - 1 then + Error "End is out of Vector length" + elif _start > _end then + Error "End should be >= Start" + else + let startIdx = uint64 _start * 1UL + let endIdx = uint64 _end * 1UL + let newLength = uint64 (_end - _start + 1) * 1UL + let newSize = getNearestUpperPowerOfTwo (uint64 newLength) * 1UL + + let rec cut (size: uint64) (pos: uint64) tree = + let sizeIdx = (uint64 size) * 1UL + + match tree with + | Node(l, r) -> + let half = size / 2UL + let halfIdx = (uint64 half) * 1UL + Node(cut half pos l, cut half (pos + halfIdx) r) + | Leaf(Dummy) -> Leaf Dummy + | Leaf(UserValue(v)) when pos >= startIdx && pos + sizeIdx - 1UL <= endIdx -> Leaf(UserValue(v)) + | _ -> Leaf Dummy + + let cutTree = cut vector.storage.size 0UL vector.storage.data + + let rec empty (size: uint64) = + match size with + | 1UL -> Leaf Dummy + | _ -> + let half = size / 2UL + let subtree = empty half + Node(subtree, subtree) + + let rec insert (size: uint64) (idx: uint64) value tree = + match size with + | 1UL -> Leaf(UserValue(value)) + | _ -> + let half = size / 2UL + let border = (uint64 half) * 1UL + + match tree with + | Node(left, right) -> + if idx < border then + Node(insert half idx value left, right) + else + Node(left, insert half (idx - border) value right) + | _ -> + let emptySub = empty half + + if idx < border then + Node(insert half idx value emptySub, emptySub) + else + Node(emptySub, insert half (idx - border) value emptySub) + + let rec rebuild (size: uint64) (pos: uint64) tree acc = + match tree with + | Leaf(Dummy) -> acc + | Leaf(UserValue(v)) -> + let newIdx = pos - startIdx + insert newSize newIdx v acc + | Node(left, right) -> + let half = size / 2UL + let border = (uint64 half) * 1UL + let acc = rebuild half pos left acc + rebuild half (pos + border) right acc + + let emptyTree = empty newSize + let shiftedTree = rebuild vector.storage.size 0UL cutTree emptyTree + + let rec count (size: uint64) tree = + match tree with + | Node(l, r) -> + let half = size / 2UL + count half l + count half r + | Leaf(UserValue(_)) -> (uint64 size) * 1UL + | _ -> 0UL + + let nvals = count newSize shiftedTree + + Ok(SparseVector(newLength, nvals, Storage(newSize, shiftedTree)))