Add an ellipse benchmark + unit test (WIP)
authorGreg Burri <greg.burri@gmail.com>
Wed, 31 Mar 2021 19:02:59 +0000 (21:02 +0200)
committerGreg Burri <greg.burri@gmail.com>
Wed, 31 Mar 2021 19:02:59 +0000 (21:02 +0200)
Parasitemia/ParasitemiaCore/Ellipse.fs
Parasitemia/ParasitemiaCore/Types.fs
Parasitemia/ParasitemiaUI/Constants.fs [new file with mode: 0644]
Parasitemia/ParasitemiaUI/ParasitemiaUI.fsproj
Parasitemia/Tests/ParasitemiaCore.Benchmark/EllipsesBench.fs [new file with mode: 0644]
Parasitemia/Tests/ParasitemiaCore.Benchmark/KdTreeBench.fs [new file with mode: 0644]
Parasitemia/Tests/ParasitemiaCore.Benchmark/ParasitemiaCore.Benchmark.fsproj
Parasitemia/Tests/ParasitemiaCore.Benchmark/Program.fs
Parasitemia/Tests/ParasitemiaCore.Tests/EllipsesTests.fs [new file with mode: 0644]
Parasitemia/Tests/ParasitemiaCore.Tests/ParasitemiaCore.Tests.fsproj

index 65264ad..5a7df2b 100644 (file)
@@ -78,6 +78,11 @@ let ellipse (p1x : float) (p1y : float) (m1 : float) (p2x : float) (p2y : float)
 
             Some (Types.Ellipse (float32 cx, float32 cy, float32 majorAxis, float32 minorAxis, float32 phi'))
 
+
+// Optimized version of 'ellipse': TODO
+let ellipse' (p1x : float) (p1y : float) (m1 : float) (p2x : float) (p2y : float) (m2 : float) (p3x : float) (p3y : float) : Types.Ellipse option =
+    ellipse p1x p1y m1 p2x p2y m2 p3x p3y
+
 let inline private vectorRotation (px : float32) (py : float32) (vx : float32) (vy : float32) (p0x : float32) (p0y : float32) : float32 =
     if py > p0y then
         if vx > 0.f then -1.f else 1.f
index 84d4548..f13043c 100644 (file)
@@ -44,7 +44,19 @@ type Ellipse (cx : float32, cy : float32, a : float32, b : float32, alpha : floa
         PI * (3.f * (this.A + this.B) - sqrt ((3.f * this.A + this.B) * (this.A + 3.f * this.B)))
 
     override this.ToString () =
-        sprintf "{Ellipse: cx = %f, cy = %f, a = %f, b = %f, alpha = %f}" this.Cx this.Cy this.A this.B this.Alpha
+        $"{{Ellipse: cx = %f{this.Cx}, cy = %f{this.Cy}, a = %f{this.A}, b = %f{this.B}, alpha = %f{this.Alpha}}}"
+
+    override this.Equals (other : obj) =
+        match other with
+        | :? Ellipse as otherEllipse ->
+            otherEllipse.Cx = this.Cx &&
+            otherEllipse.Cy = this.Cy &&
+            otherEllipse.A = this.A &&
+            otherEllipse.B = this.B &&
+            otherEllipse.Alpha = this.Alpha
+        | _ -> false
+
+    override this.GetHashCode () = HashCode.Combine (this.Cx, this.Cy, this.A, this.B, this.Alpha)
 
 [<Struct>]
 type CellClass = HealthyRBC | InfectedRBC | Peculiar
diff --git a/Parasitemia/ParasitemiaUI/Constants.fs b/Parasitemia/ParasitemiaUI/Constants.fs
new file mode 100644 (file)
index 0000000..8ad5db7
--- /dev/null
@@ -0,0 +1 @@
+module ParasitemiaUI.Constants
index 1583816..2a87c55 100644 (file)
@@ -17,6 +17,7 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <Compile Include="Constants.fs" />
     <Compile Include="Types.fs" />
     <Compile Include="Utils.fs" />
     <Compile Include="SourceImage.fs" />
diff --git a/Parasitemia/Tests/ParasitemiaCore.Benchmark/EllipsesBench.fs b/Parasitemia/Tests/ParasitemiaCore.Benchmark/EllipsesBench.fs
new file mode 100644 (file)
index 0000000..33ce745
--- /dev/null
@@ -0,0 +1,42 @@
+module ParasitemiaCore.EllipsesBench
+
+open System
+open System.Diagnostics
+open type System.Console
+
+open ParasitemiaCore.Ellipse
+
+let rng = System.Random (42)
+
+let generateParameters () : float * float * float * float * float * float * float * float =
+    rng.NextDouble (),
+    rng.NextDouble (),
+    rng.NextDouble (),
+    rng.NextDouble (),
+    rng.NextDouble (),
+    rng.NextDouble (),
+    rng.NextDouble (),
+    rng.NextDouble ()
+
+let benchOld () =
+    let sw = Stopwatch ()
+    sw.Start ()
+
+    for i in 1 .. 500_000 do
+        let p1x, p1y, m1, p2x, p2y, m2, p3x, p3y = generateParameters ()
+        ellipse p1x p1y m1 p2x p2y m2 p3x p3y |> ignore
+
+    sw.Stop ()
+    WriteLine (sprintf "Old: time = %A ms" sw.ElapsedMilliseconds)
+
+
+let benchNew () =
+    let sw = Stopwatch ()
+    sw.Start ()
+
+    for i in 1 .. 500_000 do
+        let p1x, p1y, m1, p2x, p2y, m2, p3x, p3y = generateParameters ()
+        ellipse' p1x p1y m1 p2x p2y m2 p3x p3y |> ignore
+
+    sw.Stop ()
+    WriteLine (sprintf "New: time = %A ms" sw.ElapsedMilliseconds)
\ No newline at end of file
diff --git a/Parasitemia/Tests/ParasitemiaCore.Benchmark/KdTreeBench.fs b/Parasitemia/Tests/ParasitemiaCore.Benchmark/KdTreeBench.fs
new file mode 100644 (file)
index 0000000..028939b
--- /dev/null
@@ -0,0 +1,65 @@
+module ParasitemiaCore.KdTreeBench
+
+open System
+open type System.Console
+
+open ParasitemiaCore.KdTree
+
+type Point (x : float32, y : float32) =
+    interface I2DCoords with
+        member this.X = x
+        member this.Y = y
+
+    override this.ToString () =
+        sprintf "(%.1f, %.1f)" x y
+
+let bench1 () =
+    let min = -1_000.
+    let max = +1_000.
+    let windowSize = 10.
+    let nbPoints = 1_000_000
+    let n = 1_000_000
+
+    let rng = Random 42
+    let nextNumber (min : float) (max : float) (rng : Random) =
+        (rng.NextDouble () * (max + abs min)) + min |> float32
+
+    let points =
+        [
+            for i = 1 to nbPoints do
+                let x = nextNumber min max rng
+                let y = nextNumber min max rng
+                Point (x, y)
+        ]
+
+    let sw = System.Diagnostics.Stopwatch ()
+    sw.Start ()
+
+    let tree = Tree.BuildTree points
+
+    sw.Stop ()
+    WriteLine (sprintf "Time to build = %A ms" sw.ElapsedMilliseconds)
+
+    let rng = Random 42
+    sw.Restart ()
+
+    let mutable nbFound = 0
+    for i = 1 to n do
+        let minX = nextNumber min (max - windowSize) rng
+        let minY = nextNumber min (max - windowSize) rng
+        nbFound <- nbFound + (tree.SearchOld { minX = minX; maxX = minX + float32 windowSize; minY = minY; maxY = minY + float32 windowSize } |> List.length)
+
+    sw.Stop ()
+    WriteLine (sprintf "New: nb found: %i. Time to search = %A ms" nbFound sw.ElapsedMilliseconds)
+
+    let rng = Random 42
+    sw.Restart ()
+
+    let mutable nbFound = 0
+    for i = 1 to n do
+        let minX = nextNumber min (max - windowSize) rng
+        let minY = nextNumber min (max - windowSize) rng
+        nbFound <- nbFound + (tree.Search { minX = minX; maxX = minX + float32 windowSize; minY = minY; maxY = minY + float32 windowSize }).Count
+
+    sw.Stop ()
+    WriteLine (sprintf "New: nb found: %i. Time to search = %A ms" nbFound sw.ElapsedMilliseconds)
\ No newline at end of file
index ea4034b..5db4d12 100644 (file)
@@ -6,6 +6,8 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <Compile Include="EllipsesBench.fs" />
+    <Compile Include="KdTreeBench.fs" />
     <Compile Include="Program.fs" />
   </ItemGroup>
 
index 5022108..6057f7f 100644 (file)
@@ -1,70 +1,10 @@
 // Learn more about F# at http://docs.microsoft.com/dotnet/fsharp
 
-open System
-open type System.Console
-
-open ParasitemiaCore.KdTree
-
-type Point (x : float32, y : float32) =
-    interface I2DCoords with
-        member this.X = x
-        member this.Y = y
-
-    override this.ToString () =
-        sprintf "(%.1f, %.1f)" x y
-
-let kdTree () =
-    let min = -1_000.
-    let max = +1_000.
-    let windowSize = 10.
-    let nbPoints = 1_000_000
-    let n = 1_000_000
-
-    let rng = Random 42
-    let nextNumber (min : float) (max : float) (rng : Random) =
-        (rng.NextDouble () * (max + abs min)) + min |> float32
-
-    let points =
-        [
-            for i = 1 to nbPoints do
-                let x = nextNumber min max rng
-                let y = nextNumber min max rng
-                Point (x, y)
-        ]
-
-    let sw = System.Diagnostics.Stopwatch ()
-    sw.Start ()
-
-    let tree = Tree.BuildTree points
-
-    sw.Stop ()
-    WriteLine (sprintf "Time to build = %A ms" sw.ElapsedMilliseconds)
-
-    let rng = Random 42
-    sw.Restart ()
-
-    let mutable nbFound = 0
-    for i = 1 to n do
-        let minX = nextNumber min (max - windowSize) rng
-        let minY = nextNumber min (max - windowSize) rng
-        nbFound <- nbFound + (tree.SearchOld { minX = minX; maxX = minX + float32 windowSize; minY = minY; maxY = minY + float32 windowSize } |> List.length)
-
-    sw.Stop ()
-    WriteLine (sprintf "New: nb found: %i. Time to search = %A ms" nbFound sw.ElapsedMilliseconds)
-
-    let rng = Random 42
-    sw.Restart ()
-
-    let mutable nbFound = 0
-    for i = 1 to n do
-        let minX = nextNumber min (max - windowSize) rng
-        let minY = nextNumber min (max - windowSize) rng
-        nbFound <- nbFound + (tree.Search { minX = minX; maxX = minX + float32 windowSize; minY = minY; maxY = minY + float32 windowSize }).Count
-
-    sw.Stop ()
-    WriteLine (sprintf "New: nb found: %i. Time to search = %A ms" nbFound sw.ElapsedMilliseconds)
+module ParasitemiaCore.Main
 
 [<EntryPoint>]
 let main argv =
-    kdTree ()
+    //KdTreeBench.bench1 ()
+    EllipsesBench.benchOld ()
+    EllipsesBench.benchNew ()
     0
\ No newline at end of file
diff --git a/Parasitemia/Tests/ParasitemiaCore.Tests/EllipsesTests.fs b/Parasitemia/Tests/ParasitemiaCore.Tests/EllipsesTests.fs
new file mode 100644 (file)
index 0000000..185c43d
--- /dev/null
@@ -0,0 +1,35 @@
+namespace ParasitemiaCore.Tests
+
+open Xunit
+open Xunit.Abstractions
+
+open Swensen.Unquote
+
+open ParasitemiaCore.Ellipse
+
+type EllipsesTests (output : ITestOutputHelper) =
+
+    [<Fact>]
+    member this.``Compare the old and new ellipse construction implementations`` () =
+        let rng = System.Random (42)
+
+        let generateParameters () : float * float * float * float * float * float * float * float =
+            rng.NextDouble (),
+            rng.NextDouble (),
+            rng.NextDouble (),
+            rng.NextDouble (),
+            rng.NextDouble (),
+            rng.NextDouble (),
+            rng.NextDouble (),
+            rng.NextDouble ()
+
+        for i in 1 .. 100 do
+            let p1x, p1y, m1, p2x, p2y, m2, p3x, p3y = generateParameters ()
+            let e = ellipse p1x p1y m1 p2x p2y m2 p3x p3y
+            let e' = ellipse' p1x p1y m1 p2x p2y m2 p3x p3y
+
+            output.WriteLine (string e)
+
+            test <@ e = e' @>
+
+        ()
\ No newline at end of file
index 6ef1b8e..45f05bb 100644 (file)
@@ -7,11 +7,13 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <Compile Include="EllipsesTests.fs" />
     <Compile Include="KdTreeTests.fs" />
   </ItemGroup>
 
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
+    <PackageReference Include="Unquote" Version="5.0.0" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
       <PrivateAssets>all</PrivateAssets>