From 6a8cc583c3f6d2930eed5fec039d1cecbe5cefae Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 10 Oct 2023 14:28:13 +0200 Subject: [PATCH 01/45] [Tests] Update packages --- paket.dependencies | 14 +++++------ paket.lock | 62 +++++++++++++++++++++++----------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/paket.dependencies b/paket.dependencies index 0d8b738c..639c2127 100644 --- a/paket.dependencies +++ b/paket.dependencies @@ -33,13 +33,13 @@ group Test source https://api.nuget.org/v3/index.json nuget NUnit ~> 3.13.3 - nuget FsUnit ~> 5.2.0 + nuget FsUnit ~> 5.4.0 nuget FsCheck ~> 2.16.4 nuget FsCheck.NUnit ~> 2.16.4 - nuget NUnit3TestAdapter ~> 4.4.2 - nuget Microsoft.NET.Test.Sdk ~> 17.5.0 - nuget Expecto ~> 9.0.4 - nuget Expecto.FsCheck ~> 9.0.4 - nuget YoloDev.Expecto.TestSdk ~> 0.13.3 + nuget NUnit3TestAdapter ~> 4.5.0 + nuget Microsoft.NET.Test.Sdk ~> 17.7.2 + nuget Expecto ~> 10.1.0 + nuget Expecto.FsCheck ~> 10.1.0 + nuget YoloDev.Expecto.TestSdk ~> 0.14.2 - nuget BenchmarkDotNet ~> 0.13.1 \ No newline at end of file + nuget BenchmarkDotNet ~> 0.13.9 \ No newline at end of file diff --git a/paket.lock b/paket.lock index f5969f49..83422163 100644 --- a/paket.lock +++ b/paket.lock @@ -201,33 +201,33 @@ GROUP Test RESTRICTION: || (== net6.0) (== net6.0-windows7.0) NUGET remote: https://api.nuget.org/v3/index.json - BenchmarkDotNet (0.13.5) - BenchmarkDotNet.Annotations (>= 0.13.5) - CommandLineParser (>= 2.4.3) + BenchmarkDotNet (0.13.9) + BenchmarkDotNet.Annotations (>= 0.13.9) + CommandLineParser (>= 2.9.1) Gee.External.Capstone (>= 2.3) Iced (>= 1.17) - Microsoft.CodeAnalysis.CSharp (>= 3.0) + Microsoft.CodeAnalysis.CSharp (>= 4.1) Microsoft.Diagnostics.Runtime (>= 2.2.332302) Microsoft.Diagnostics.Tracing.TraceEvent (>= 3.0.2) Microsoft.DotNet.PlatformAbstractions (>= 3.1.6) - Perfolizer (>= 0.2.1) - System.Management (>= 6.0) - BenchmarkDotNet.Annotations (0.13.5) + Perfolizer (0.2.1) + System.Management (>= 5.0) + BenchmarkDotNet.Annotations (0.13.9) CommandLineParser (2.9.1) - Expecto (9.0.4) - FSharp.Core (>= 4.6) - Mono.Cecil (>= 0.11.3) - Expecto.FsCheck (9.0.4) - Expecto (>= 9.0.4) - FsCheck (>= 2.14.3) - FsCheck (2.16.5) + Expecto (10.1) + FSharp.Core (>= 7.0.200) + Mono.Cecil (>= 0.11.4 < 1.0) + Expecto.FsCheck (10.1) + Expecto (>= 10.1) + FsCheck (>= 2.16.5 < 3.0) + FsCheck (2.16.6) FSharp.Core (>= 4.2.3) - FsCheck.NUnit (2.16.5) - FsCheck (2.16.5) + FsCheck.NUnit (2.16.6) + FsCheck (2.16.6) NUnit (>= 3.13.1 < 4.0) FSharp.Core (7.0.200) - FsUnit (5.2) - FSharp.Core (>= 6.0.7) + FsUnit (5.4) + FSharp.Core (>= 5.0.2) NUnit (>= 3.13.3 < 3.14) Gee.External.Capstone (2.3) Iced (1.17) @@ -243,7 +243,7 @@ NUGET System.Threading.Tasks.Extensions (>= 4.5.4) Microsoft.CodeAnalysis.CSharp (4.2) Microsoft.CodeAnalysis.Common (4.2) - Microsoft.CodeCoverage (17.5) + Microsoft.CodeCoverage (17.7.2) Microsoft.Diagnostics.NETCore.Client (0.2.327302) Microsoft.Bcl.AsyncInterfaces (>= 1.1) Microsoft.Extensions.Logging (>= 2.1.1) @@ -270,24 +270,24 @@ NUGET Microsoft.Extensions.Primitives (>= 6.0) Microsoft.Extensions.Primitives (6.0) System.Runtime.CompilerServices.Unsafe (>= 6.0) - Microsoft.NET.Test.Sdk (17.5) - Microsoft.CodeCoverage (>= 17.5) - Microsoft.TestPlatform.TestHost (>= 17.5) + Microsoft.NET.Test.Sdk (17.7.2) + Microsoft.CodeCoverage (>= 17.7.2) + Microsoft.TestPlatform.TestHost (>= 17.7.2) Microsoft.NETCore.Platforms (6.0.3) - Microsoft.TestPlatform.ObjectModel (17.5) - NuGet.Frameworks (>= 5.11) + Microsoft.TestPlatform.ObjectModel (17.7.2) + NuGet.Frameworks (>= 6.5) System.Reflection.Metadata (>= 1.6) - Microsoft.TestPlatform.TestHost (17.5) - Microsoft.TestPlatform.ObjectModel (>= 17.5) + Microsoft.TestPlatform.TestHost (17.7.2) + Microsoft.TestPlatform.ObjectModel (>= 17.7.2) Newtonsoft.Json (>= 13.0.1) Mono.Cecil (0.11.4) NETStandard.Library (2.0.3) Microsoft.NETCore.Platforms (>= 1.1) Newtonsoft.Json (13.0.1) - NuGet.Frameworks (6.2) + NuGet.Frameworks (6.7) NUnit (3.13.3) NETStandard.Library (>= 2.0) - NUnit3TestAdapter (4.4.2) + NUnit3TestAdapter (4.5) Perfolizer (0.2.1) System.Memory (>= 4.5.3) System.CodeDom (6.0) @@ -304,7 +304,7 @@ NUGET System.Text.Encoding.CodePages (6.0) System.Runtime.CompilerServices.Unsafe (>= 6.0) System.Threading.Tasks.Extensions (4.5.4) - YoloDev.Expecto.TestSdk (0.13.3) - Expecto (>= 9.0 < 10.0) - FSharp.Core (>= 4.6.2) + YoloDev.Expecto.TestSdk (0.14.2) + Expecto (>= 10.0 < 11.0) + FSharp.Core (>= 7.0.200) System.Collections.Immutable (>= 6.0) From 17ecd169fda177acf05f3d04709882d3667a0539 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 10 Oct 2023 17:14:31 +0200 Subject: [PATCH 02/45] [MapExt] Add value variants of some operations --- .../Datastructures/Immutable/MapExt.fs | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/Aardvark.Base.FSharp/Datastructures/Immutable/MapExt.fs b/src/Aardvark.Base.FSharp/Datastructures/Immutable/MapExt.fs index be9a614a..8695e870 100644 --- a/src/Aardvark.Base.FSharp/Datastructures/Immutable/MapExt.fs +++ b/src/Aardvark.Base.FSharp/Datastructures/Immutable/MapExt.fs @@ -1681,12 +1681,19 @@ module internal MapExtImplementation = let ofList comparer l = List.fold (fun acc (k,v) -> add comparer k v acc) empty l + let ofListV comparer l = List.fold (fun acc (struct (k,v)) -> add comparer k v acc) empty l let rec mkFromEnumerator comparer acc (e : IEnumerator<_>) = if e.MoveNext() then let (x,y) = e.Current mkFromEnumerator comparer (add comparer x y acc) e else acc + + let rec mkFromEnumeratorV comparer acc (e : IEnumerator<_>) = + if e.MoveNext() then + let struct (x,y) = e.Current + mkFromEnumeratorV comparer (add comparer x y acc) e + else acc let ofArray comparer (arr : array<_>) = let mutable res = empty @@ -1694,13 +1701,27 @@ module internal MapExtImplementation = res <- add comparer x y res res + let ofArrayV comparer (arr : array<_>) = + let mutable res = empty + for struct (x,y) in arr do + res <- add comparer x y res + res + let ofSeq comparer (c : seq<'Key * 'T>) = match c with | :? array<'Key * 'T> as xs -> ofArray comparer xs | :? list<'Key * 'T> as xs -> ofList comparer xs | _ -> use ie = c.GetEnumerator() - mkFromEnumerator comparer empty ie + mkFromEnumerator comparer empty ie + + let ofSeqV comparer (c : seq) = + match c with + | :? array as xs -> ofArrayV comparer xs + | :? list as xs -> ofListV comparer xs + | _ -> + use ie = c.GetEnumerator() + mkFromEnumeratorV comparer empty ie let copyToArray s (arr: _[]) i = @@ -1948,6 +1969,10 @@ type MapExt<[]'Key,[) : MapExt<'Key,'Value> = let comparer = LanguagePrimitives.FastGenericComparer<'Key> new MapExt<_,_>(comparer,MapTree.ofSeq comparer ie) + + static member CreateV(ie : IEnumerable<_>) : MapExt<'Key,'Value> = + let comparer = LanguagePrimitives.FastGenericComparer<'Key> + new MapExt<_,_>(comparer,MapTree.ofSeqV comparer ie) static member Create() : MapExt<'Key,'Value> = empty @@ -2153,6 +2178,10 @@ type MapExt<[]'Key,[ = let comparer = LanguagePrimitives.FastGenericComparer<'Key> new MapExt<_,_>(comparer,MapTree.ofList comparer l) + + static member ofListV(l) : MapExt<'Key,'Value> = + let comparer = LanguagePrimitives.FastGenericComparer<'Key> + new MapExt<_,_>(comparer,MapTree.ofListV comparer l) member this.ComputeHashCode() = let combineHash x y = (x <<< 1) + y + 631 @@ -2351,9 +2380,15 @@ module MapExt = [] let ofList (l: ('Key * 'Value) list) = MapExt<_,_>.ofList(l) + [] + let ofListV (l: (struct ('Key * 'Value)) list) = MapExt<_,_>.ofListV(l) + [] let ofSeq l = MapExt<_,_>.Create(l) - + + [] + let ofSeqV l = MapExt<_,_>.CreateV(l) + [] let singleton k v = MapExt<_,_>(LanguagePrimitives.FastGenericComparer<_>,MapOne(k,v)) @@ -2362,6 +2397,11 @@ module MapExt = let comparer = LanguagePrimitives.FastGenericComparer<'Key> new MapExt<_,_>(comparer,MapTree.ofArray comparer array) + [] + let ofArrayV (array: (struct ('Key * 'Value)) array) = + let comparer = LanguagePrimitives.FastGenericComparer<'Key> + new MapExt<_,_>(comparer,MapTree.ofArrayV comparer array) + [] let toList (m:MapExt<_,_>) = m.ToList() @@ -2388,6 +2428,9 @@ module MapExt = [] let tryMin (m:MapExt<_,_>) = m.TryMinKey + + [] + let tryMinV (m:MapExt<_,_>) = m.TryMinKeyV [] let min (m:MapExt<_,_>) = @@ -2398,6 +2441,9 @@ module MapExt = [] let tryMax (m:MapExt<_,_>) = m.TryMaxKey + [] + let tryMaxV (m:MapExt<_,_>) = m.TryMaxKeyV + [] let max (m:MapExt<_,_>) = match m.TryMaxKey with @@ -2429,6 +2475,9 @@ module MapExt = [] let split k (m:MapExt<_,_>) = m.Split k + [] + let splitV k (m:MapExt<_,_>) = m.SplitV k + [] let tryIndexOf i (m:MapExt<_,_>) = m.TryIndexOf i From da19eb6f2c98c79e9609f8c42c13efc19ce1fe9d Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 11 Oct 2023 16:09:28 +0200 Subject: [PATCH 03/45] [MapExt] Add range --- .../Datastructures/Immutable/MapExt.fs | 40 +++++++++++++++++++ .../Aardvark.Base.FSharp.Tests/MapExt.fs | 10 ++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/Aardvark.Base.FSharp/Datastructures/Immutable/MapExt.fs b/src/Aardvark.Base.FSharp/Datastructures/Immutable/MapExt.fs index 8695e870..f8eea6f1 100644 --- a/src/Aardvark.Base.FSharp/Datastructures/Immutable/MapExt.fs +++ b/src/Aardvark.Base.FSharp/Datastructures/Immutable/MapExt.fs @@ -455,6 +455,40 @@ module internal MapExtImplementation = | ValueSome v -> join left k v right | ValueNone -> merge left right + let rec range (comparer : IComparer<'Value>) lk rk m = + match m with + | _ when comparer.Compare(lk, rk) > 0 -> + MapEmpty + + | MapEmpty -> + MapEmpty + + | MapOne(k, _) as n -> + if comparer.Compare(lk, k) <= 0 && comparer.Compare(k, rk) <= 0 then n + else MapEmpty + + | MapNode(k, v, l, r, _, _) -> + let cl = comparer.Compare(lk, k) + + if cl = 0 then + if comparer.Compare(k, rk) = 0 then + MapOne(k, v) + else + join MapEmpty k v (range comparer lk rk r) + + elif cl < 0 then + let cr = comparer.Compare(k, rk) + + if cr = 0 then + join (range comparer lk rk l) k v MapEmpty + elif cr < 0 then + join (range comparer lk rk l) k v (range comparer lk rk r) + else + range comparer lk rk l + + else + range comparer lk rk r + let rec split (comparer: IComparer<'Value>) k m = match m with | MapEmpty -> @@ -2110,6 +2144,9 @@ type MapExt<[]'Key,[ ValueOption.map (fun struct(_,v) -> v) member x.TryMaxValueV = MapTree.tryMaxV tree |> ValueOption.map (fun struct(_,v) -> v) + member x.Range(min, max) = + MapExt<'Key, 'Value>(comparer, MapTree.range comparer min max tree) + member x.Split (k) = let l, self, r = MapTree.split comparer k tree MapExt<'Key, 'Value>(comparer, l), self, MapExt<'Key, 'Value>(comparer, r) @@ -2472,6 +2509,9 @@ module MapExt = [] let chooseMonotonic f (m:MapExt<_,_>) = m.ChooseMonotonic(f) + [] + let range min max (m:MapExt<_,_>) = m.Range(min, max) + [] let split k (m:MapExt<_,_>) = m.Split k diff --git a/src/Tests/Aardvark.Base.FSharp.Tests/MapExt.fs b/src/Tests/Aardvark.Base.FSharp.Tests/MapExt.fs index 786519bb..0ff7e473 100644 --- a/src/Tests/Aardvark.Base.FSharp.Tests/MapExt.fs +++ b/src/Tests/Aardvark.Base.FSharp.Tests/MapExt.fs @@ -46,5 +46,11 @@ let ``[MapExt] choose`` (m : Map) (f : int -> int -> Option) = MapExt.toList (MapExt.choose f me) = Map.toList (choose f m) ] - - +[] +let ``[MapExt] range`` (m : Map) (min : int) (max : int) = + (min <= max) ==> lazy ( + let me = MapExt.ofSeq (Map.toSeq m) + let expected = me |> MapExt.filter (fun k _ -> k >= min && k <= max) + let actual = me |> MapExt.range min max + expected = actual + ) \ No newline at end of file From b728d40de86fcd383183067563b067996016b647 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 12 Oct 2023 12:56:45 +0200 Subject: [PATCH 04/45] Add fstv and sndv --- src/Aardvark.Base.FSharp/Prelude/FSLibExtensions.fs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Aardvark.Base.FSharp/Prelude/FSLibExtensions.fs b/src/Aardvark.Base.FSharp/Prelude/FSLibExtensions.fs index c532e3e0..53d381e9 100644 --- a/src/Aardvark.Base.FSharp/Prelude/FSLibExtensions.fs +++ b/src/Aardvark.Base.FSharp/Prelude/FSLibExtensions.fs @@ -166,6 +166,11 @@ module Prelude = match option with | Some value -> value | None -> fallback + + [] + module ValueOptionOperators = + let inline fstv (struct (x, _)) = x + let inline sndv (struct (_, y)) = y type Async with static member AwaitTask(t : System.Threading.Tasks.Task) = From dccdf8a7d8e53591305a781d477e782fc895e25d Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 12 Oct 2023 17:47:23 +0200 Subject: [PATCH 05/45] Improve RangeSet implementation Based on implementation in Aardvark.Rendering.Vulkan --- .../Aardvark.Base.FSharp.fsproj | 2 + .../Immutable/RangeSetOld_auto.fs | 455 ++++ .../Immutable/RangeSetOld_template.fs | 242 +++ .../Datastructures/Immutable/RangeSet_auto.fs | 1847 ++++++++++++++--- .../Immutable/RangeSet_template.fs | 574 +++-- .../Aardvark.Base.FSharp.Benchmarks.fsproj | 7 +- .../RangeSet.fs | 275 +++ .../paket.references | 1 + 8 files changed, 2909 insertions(+), 494 deletions(-) create mode 100644 src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSetOld_auto.fs create mode 100644 src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSetOld_template.fs create mode 100644 src/Tests/Aardvark.Base.FSharp.Benchmarks/RangeSet.fs diff --git a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj index 24d5d254..0bbf51df 100644 --- a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj +++ b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj @@ -43,6 +43,8 @@ + + diff --git a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSetOld_auto.fs b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSetOld_auto.fs new file mode 100644 index 00000000..2d1137f3 --- /dev/null +++ b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSetOld_auto.fs @@ -0,0 +1,455 @@ +namespace Aardvark.Base + +open System +open System.Collections +open System.Collections.Generic +open FingerTreeImplementation + +#nowarn "44" + +[] +type private HalfRange = + struct + val mutable public IsMax : bool + val mutable public Value : int32 + + new(m,v) = { IsMax = m; Value = v } + + override x.GetHashCode() = + if x.IsMax then 0 + else x.Value.GetHashCode() + + override x.Equals o = + match o with + | :? HalfRange as o -> + x.IsMax = o.IsMax && x.Value = o.Value + | _ -> + false + + member x.CompareTo (o : HalfRange) = + let c = x.Value.CompareTo o.Value + if c = 0 then + if x.IsMax = o.IsMax then 0 + else (if x.IsMax then 1 else -1) + else + c + + interface IComparable with + member x.CompareTo o = + match o with + | :? HalfRange as o -> x.CompareTo(o) + | _ -> failwith "uncomparable" + end + + +[] +[] +type RangeSet = private { root : FingerTreeNode } with + + member private x.AsString = + x |> Seq.map (sprintf "%A") + |> String.concat "; " + |> sprintf "set [%s]" + + member x.Min = + match x.root |> FingerTreeNode.firstOpt with + | Some f -> f.Value + | _ -> Int32.MaxValue + + member x.Max = + match x.root |> FingerTreeNode.lastOpt with + | Some f -> f.Value + | _ -> Int32.MinValue + + member x.Range = + match FingerTreeNode.firstOpt x.root, FingerTreeNode.lastOpt x.root with + | Some min, Some max -> Range1i(min.Value, max.Value) + | _ -> Range1i.Invalid + + interface IEnumerable with + member x.GetEnumerator() = new RangeSetEnumerator(FingerTreeImplementation.FingerTreeNode.getEnumeratorFw x.root) :> IEnumerator + + interface IEnumerable with + member x.GetEnumerator() = new RangeSetEnumerator(FingerTreeImplementation.FingerTreeNode.getEnumeratorFw x.root) :> _ + + +and private RangeSetEnumerator(i : IEnumerator) = + + let mutable last = HalfRange() + let mutable current = HalfRange() + + member x.Current = Range1i(last.Value, current.Value - 1) + + interface IEnumerator with + member x.MoveNext() = + if i.MoveNext() then + last <- i.Current + if i.MoveNext() then + current <- i.Current + true + else + false + else + false + + member x.Current = x.Current :> obj + + member x.Reset() = + i.Reset() + + interface IEnumerator with + member x.Current = x.Current + member x.Dispose() = i.Dispose() + +[] +[] +module RangeSet = + let private mm = + { + quantify = fun (r : HalfRange) -> r + mempty = HalfRange(false, Int32.MinValue) + mappend = fun l r -> if l.CompareTo r > 0 then l else r + } + + let private minRange = HalfRange(false, Int32.MinValue) + + let inline private leq v = HalfRange(true, v) + let inline private geq v = HalfRange(false, v) + + let inline private (|Leq|Geq|) (r : HalfRange) = + if r.IsMax then Leq r.Value + else Geq r.Value + + let empty = { root = Empty } + + let insert (range : Range1i) (t : RangeSet) = + let rangeMax = range.Max + 1 + + let (l,rest) = t.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo range.Min >= 0) minRange + let (_,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax > 0) minRange + + let max = leq rangeMax + let min = geq range.Min + + match FingerTreeNode.lastOpt l, FingerTreeNode.firstOpt r with + | None, None -> + { root = Deep(max, One(min), Empty, One(max)) } + + | Some lmax, None -> + match lmax with + | Leq _ -> { root = l |> FingerTreeNode.append mm min |> FingerTreeNode.append mm max } + | Geq _ -> { root = l |> FingerTreeNode.append mm max } + + | None, Some rmin -> + match rmin with + | Leq _ -> { root = r |> FingerTreeNode.prepend mm min } + | Geq _ -> { root = r |> FingerTreeNode.prepend mm max |> FingerTreeNode.prepend mm min } + + | Some lmax, Some rmin -> + match lmax, rmin with + | Leq _, Geq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [min;max] r } + + | Geq _, Leq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [] r } + + | Leq _, Leq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [min] r } + + | Geq _, Geq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [max] r } + + let remove (range : Range1i) (t : RangeSet) = + let rangeMax = range.Max + 1 + + let (l,rest) = t.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo range.Min >= 0) minRange + let (_,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax > 0) minRange + + let max = geq rangeMax + let min = leq range.Min + + match FingerTreeNode.lastOpt l, FingerTreeNode.firstOpt r with + | None, None -> + { root = Empty } + + | Some lmax, None -> + match lmax with + | Leq _ -> { root = l } + | Geq _ -> { root = l |> FingerTreeNode.append mm min } + + | None, Some rmin -> + match rmin with + | Leq _ -> { root = r |> FingerTreeNode.prepend mm max } + | Geq _ -> { root = r } + + | Some lmax, Some rmin -> + match lmax, rmin with + | Leq _, Geq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [] r } + + | Geq _, Leq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [min; max] r } + + | Leq _, Leq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [max] r } + + | Geq _, Geq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [min] r } + + let ofSeq (s : seq) = + let mutable res = empty + for e in s do res <- insert e res + res + + let inline ofList (l : list) = ofSeq l + let inline ofArray (l : Range1i[]) = ofSeq l + + let toSeq (r : RangeSet) = r :> seq<_> + let toList (r : RangeSet) = r |> Seq.toList + let toArray (r : RangeSet) = r |> Seq.toArray + + let inline min (t : RangeSet) = t.Min + let inline max (t : RangeSet) = t.Max + let inline range (t : RangeSet) = t.Range + + let window (window : Range1i) (set : RangeSet) = + let rangeMax = window.Max + 1 + + let (l,rest) = set.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo window.Min > 0) minRange + let (inner,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax >= 0) minRange + + let inner = + match FingerTreeNode.lastOpt l with + | Some (Geq _) -> FingerTreeNode.prepend mm (geq window.Min) inner + | _ -> inner + + let inner = + match FingerTreeNode.firstOpt r with + | Some (Leq _) -> FingerTreeNode.append mm (leq rangeMax) inner + | _ -> inner + + { root = inner } + +[] +type private HalfRange64 = + struct + val mutable public IsMax : bool + val mutable public Value : int64 + + new(m,v) = { IsMax = m; Value = v } + + override x.GetHashCode() = + if x.IsMax then 0 + else x.Value.GetHashCode() + + override x.Equals o = + match o with + | :? HalfRange64 as o -> + x.IsMax = o.IsMax && x.Value = o.Value + | _ -> + false + + member x.CompareTo (o : HalfRange64) = + let c = x.Value.CompareTo o.Value + if c = 0 then + if x.IsMax = o.IsMax then 0 + else (if x.IsMax then 1 else -1) + else + c + + interface IComparable with + member x.CompareTo o = + match o with + | :? HalfRange64 as o -> x.CompareTo(o) + | _ -> failwith "uncomparable" + end + + +[] +[] +type RangeSet64 = private { root : FingerTreeNode } with + + member private x.AsString = + x |> Seq.map (sprintf "%A") + |> String.concat "; " + |> sprintf "set [%s]" + + member x.Min = + match x.root |> FingerTreeNode.firstOpt with + | Some f -> f.Value + | _ -> Int64.MaxValue + + member x.Max = + match x.root |> FingerTreeNode.lastOpt with + | Some f -> f.Value + | _ -> Int64.MinValue + + member x.Range = + match FingerTreeNode.firstOpt x.root, FingerTreeNode.lastOpt x.root with + | Some min, Some max -> Range1l(min.Value, max.Value) + | _ -> Range1l.Invalid + + interface IEnumerable with + member x.GetEnumerator() = new RangeSet64Enumerator(FingerTreeImplementation.FingerTreeNode.getEnumeratorFw x.root) :> IEnumerator + + interface IEnumerable with + member x.GetEnumerator() = new RangeSet64Enumerator(FingerTreeImplementation.FingerTreeNode.getEnumeratorFw x.root) :> _ + + +and private RangeSet64Enumerator(i : IEnumerator) = + + let mutable last = HalfRange64() + let mutable current = HalfRange64() + + member x.Current = Range1l(last.Value, current.Value - 1L) + + interface IEnumerator with + member x.MoveNext() = + if i.MoveNext() then + last <- i.Current + if i.MoveNext() then + current <- i.Current + true + else + false + else + false + + member x.Current = x.Current :> obj + + member x.Reset() = + i.Reset() + + interface IEnumerator with + member x.Current = x.Current + member x.Dispose() = i.Dispose() + +[] +[] +module RangeSet64 = + let private mm = + { + quantify = fun (r : HalfRange64) -> r + mempty = HalfRange64(false, Int64.MinValue) + mappend = fun l r -> if l.CompareTo r > 0 then l else r + } + + let private minRange = HalfRange64(false, Int64.MinValue) + + let inline private leq v = HalfRange64(true, v) + let inline private geq v = HalfRange64(false, v) + + let inline private (|Leq|Geq|) (r : HalfRange64) = + if r.IsMax then Leq r.Value + else Geq r.Value + + let empty = { root = Empty } + + let insert (range : Range1l) (t : RangeSet64) = + let rangeMax = range.Max + 1L + + let (l,rest) = t.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo range.Min >= 0) minRange + let (_,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax > 0) minRange + + let max = leq rangeMax + let min = geq range.Min + + match FingerTreeNode.lastOpt l, FingerTreeNode.firstOpt r with + | None, None -> + { root = Deep(max, One(min), Empty, One(max)) } + + | Some lmax, None -> + match lmax with + | Leq _ -> { root = l |> FingerTreeNode.append mm min |> FingerTreeNode.append mm max } + | Geq _ -> { root = l |> FingerTreeNode.append mm max } + + | None, Some rmin -> + match rmin with + | Leq _ -> { root = r |> FingerTreeNode.prepend mm min } + | Geq _ -> { root = r |> FingerTreeNode.prepend mm max |> FingerTreeNode.prepend mm min } + + | Some lmax, Some rmin -> + match lmax, rmin with + | Leq _, Geq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [min;max] r } + + | Geq _, Leq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [] r } + + | Leq _, Leq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [min] r } + + | Geq _, Geq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [max] r } + + let remove (range : Range1l) (t : RangeSet64) = + let rangeMax = range.Max + 1L + + let (l,rest) = t.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo range.Min >= 0) minRange + let (_,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax > 0) minRange + + let max = geq rangeMax + let min = leq range.Min + + match FingerTreeNode.lastOpt l, FingerTreeNode.firstOpt r with + | None, None -> + { root = Empty } + + | Some lmax, None -> + match lmax with + | Leq _ -> { root = l } + | Geq _ -> { root = l |> FingerTreeNode.append mm min } + + | None, Some rmin -> + match rmin with + | Leq _ -> { root = r |> FingerTreeNode.prepend mm max } + | Geq _ -> { root = r } + + | Some lmax, Some rmin -> + match lmax, rmin with + | Leq _, Geq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [] r } + + | Geq _, Leq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [min; max] r } + + | Leq _, Leq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [max] r } + + | Geq _, Geq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [min] r } + + let ofSeq (s : seq) = + let mutable res = empty + for e in s do res <- insert e res + res + + let inline ofList (l : list) = ofSeq l + let inline ofArray (l : Range1l[]) = ofSeq l + + let toSeq (r : RangeSet64) = r :> seq<_> + let toList (r : RangeSet64) = r |> Seq.toList + let toArray (r : RangeSet64) = r |> Seq.toArray + + let inline min (t : RangeSet64) = t.Min + let inline max (t : RangeSet64) = t.Max + let inline range (t : RangeSet64) = t.Range + + let window (window : Range1l) (set : RangeSet64) = + let rangeMax = window.Max + 1L + + let (l,rest) = set.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo window.Min > 0) minRange + let (inner,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax >= 0) minRange + + let inner = + match FingerTreeNode.lastOpt l with + | Some (Geq _) -> FingerTreeNode.prepend mm (geq window.Min) inner + | _ -> inner + + let inner = + match FingerTreeNode.firstOpt r with + | Some (Leq _) -> FingerTreeNode.append mm (leq rangeMax) inner + | _ -> inner + + { root = inner } + diff --git a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSetOld_template.fs b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSetOld_template.fs new file mode 100644 index 00000000..759b6e7f --- /dev/null +++ b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSetOld_template.fs @@ -0,0 +1,242 @@ +namespace Aardvark.Base + +open System +open System.Collections +open System.Collections.Generic +open FingerTreeImplementation + +#nowarn "44" + +//# foreach (var isLong in new[] { false, true }) { +//# var halfrange = isLong ? "HalfRange64" : "HalfRange"; +//# var rangeset = isLong ? "RangeSet64" : "RangeSet"; +//# var rangesetenumerator = isLong ? "RangeSet64Enumerator" : "RangeSetEnumerator"; +//# var range = isLong ? "Range1l" : "Range1i"; +//# var systype = isLong ? "Int64" : "Int32"; +//# var ft = isLong ? "int64" : "int32"; +//# var one = isLong ? "1L" : "1"; +//# var replacement = isLong ? "RangeSet1l" : "RangeSet1i"; +[] +type private __halfrange__ = + struct + val mutable public IsMax : bool + val mutable public Value : __ft__ + + new(m,v) = { IsMax = m; Value = v } + + override x.GetHashCode() = + if x.IsMax then 0 + else x.Value.GetHashCode() + + override x.Equals o = + match o with + | :? __halfrange__ as o -> + x.IsMax = o.IsMax && x.Value = o.Value + | _ -> + false + + member x.CompareTo (o : __halfrange__) = + let c = x.Value.CompareTo o.Value + if c = 0 then + if x.IsMax = o.IsMax then 0 + else (if x.IsMax then 1 else -1) + else + c + + interface IComparable with + member x.CompareTo o = + match o with + | :? __halfrange__ as o -> x.CompareTo(o) + | _ -> failwith "uncomparable" + end + + +[] +[] +type __rangeset__ = private { root : FingerTreeNode<__halfrange__, __halfrange__> } with + + member private x.AsString = + x |> Seq.map (sprintf "%A") + |> String.concat "; " + |> sprintf "set [%s]" + + member x.Min = + match x.root |> FingerTreeNode.firstOpt with + | Some f -> f.Value + | _ -> __systype__.MaxValue + + member x.Max = + match x.root |> FingerTreeNode.lastOpt with + | Some f -> f.Value + | _ -> __systype__.MinValue + + member x.Range = + match FingerTreeNode.firstOpt x.root, FingerTreeNode.lastOpt x.root with + | Some min, Some max -> __range__(min.Value, max.Value) + | _ -> __range__.Invalid + + interface IEnumerable with + member x.GetEnumerator() = new __rangesetenumerator__(FingerTreeImplementation.FingerTreeNode.getEnumeratorFw x.root) :> IEnumerator + + interface IEnumerable<__range__> with + member x.GetEnumerator() = new __rangesetenumerator__(FingerTreeImplementation.FingerTreeNode.getEnumeratorFw x.root) :> _ + + +and private __rangesetenumerator__(i : IEnumerator<__halfrange__>) = + + let mutable last = __halfrange__() + let mutable current = __halfrange__() + + member x.Current = __range__(last.Value, current.Value - __one__) + + interface IEnumerator with + member x.MoveNext() = + if i.MoveNext() then + last <- i.Current + if i.MoveNext() then + current <- i.Current + true + else + false + else + false + + member x.Current = x.Current :> obj + + member x.Reset() = + i.Reset() + + interface IEnumerator<__range__> with + member x.Current = x.Current + member x.Dispose() = i.Dispose() + +[] +[] +module __rangeset__ = + let private mm = + { + quantify = fun (r : __halfrange__) -> r + mempty = __halfrange__(false, __systype__.MinValue) + mappend = fun l r -> if l.CompareTo r > 0 then l else r + } + + let private minRange = __halfrange__(false, __systype__.MinValue) + + let inline private leq v = __halfrange__(true, v) + let inline private geq v = __halfrange__(false, v) + + let inline private (|Leq|Geq|) (r : __halfrange__) = + if r.IsMax then Leq r.Value + else Geq r.Value + + let empty = { root = Empty } + + let insert (range : __range__) (t : __rangeset__) = + let rangeMax = range.Max + __one__ + + let (l,rest) = t.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo range.Min >= 0) minRange + let (_,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax > 0) minRange + + let max = leq rangeMax + let min = geq range.Min + + match FingerTreeNode.lastOpt l, FingerTreeNode.firstOpt r with + | None, None -> + { root = Deep(max, One(min), Empty, One(max)) } + + | Some lmax, None -> + match lmax with + | Leq _ -> { root = l |> FingerTreeNode.append mm min |> FingerTreeNode.append mm max } + | Geq _ -> { root = l |> FingerTreeNode.append mm max } + + | None, Some rmin -> + match rmin with + | Leq _ -> { root = r |> FingerTreeNode.prepend mm min } + | Geq _ -> { root = r |> FingerTreeNode.prepend mm max |> FingerTreeNode.prepend mm min } + + | Some lmax, Some rmin -> + match lmax, rmin with + | Leq _, Geq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [min;max] r } + + | Geq _, Leq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [] r } + + | Leq _, Leq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [min] r } + + | Geq _, Geq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [max] r } + + let remove (range : __range__) (t : __rangeset__) = + let rangeMax = range.Max + __one__ + + let (l,rest) = t.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo range.Min >= 0) minRange + let (_,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax > 0) minRange + + let max = geq rangeMax + let min = leq range.Min + + match FingerTreeNode.lastOpt l, FingerTreeNode.firstOpt r with + | None, None -> + { root = Empty } + + | Some lmax, None -> + match lmax with + | Leq _ -> { root = l } + | Geq _ -> { root = l |> FingerTreeNode.append mm min } + + | None, Some rmin -> + match rmin with + | Leq _ -> { root = r |> FingerTreeNode.prepend mm max } + | Geq _ -> { root = r } + + | Some lmax, Some rmin -> + match lmax, rmin with + | Leq _, Geq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [] r } + + | Geq _, Leq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [min; max] r } + + | Leq _, Leq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [max] r } + + | Geq _, Geq _ -> + { root = FingerTreeNode.concatWithMiddle mm l [min] r } + + let ofSeq (s : seq<__range__>) = + let mutable res = empty + for e in s do res <- insert e res + res + + let inline ofList (l : list<__range__>) = ofSeq l + let inline ofArray (l : __range__[]) = ofSeq l + + let toSeq (r : __rangeset__) = r :> seq<_> + let toList (r : __rangeset__) = r |> Seq.toList + let toArray (r : __rangeset__) = r |> Seq.toArray + + let inline min (t : __rangeset__) = t.Min + let inline max (t : __rangeset__) = t.Max + let inline range (t : __rangeset__) = t.Range + + let window (window : __range__) (set : __rangeset__) = + let rangeMax = window.Max + __one__ + + let (l,rest) = set.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo window.Min > 0) minRange + let (inner,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax >= 0) minRange + + let inner = + match FingerTreeNode.lastOpt l with + | Some (Geq _) -> FingerTreeNode.prepend mm (geq window.Min) inner + | _ -> inner + + let inner = + match FingerTreeNode.firstOpt r with + | Some (Leq _) -> FingerTreeNode.append mm (leq rangeMax) inner + | _ -> inner + + { root = inner } + +//# } \ No newline at end of file diff --git a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_auto.fs b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_auto.fs index 0eff6f36..cc399c3d 100644 --- a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_auto.fs +++ b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_auto.fs @@ -3,447 +3,1664 @@ namespace Aardvark.Base open System open System.Collections open System.Collections.Generic -open FingerTreeImplementation +open Aardvark.Base -[] -type private HalfRange = - struct - val mutable public IsMax : bool - val mutable public Value : int32 +type internal HalfRangeKind = + | Left = 0 + | Right = 1 - new(m,v) = { IsMax = m; Value = v } +module private RangeSetUtils = - override x.GetHashCode() = - if x.IsMax then 0 - else x.Value.GetHashCode() + let inline inc (value : 'T) = + let res = value + LanguagePrimitives.GenericOne<'T> + if res = Constant<'T>.ParseableMinValue then struct (Constant<'T>.ParseableMaxValue, true) + else struct (res, false) - override x.Equals o = - match o with - | :? HalfRange as o -> - x.IsMax = o.IsMax && x.Value = o.Value - | _ -> - false + module MapExt = + let inline splitAt (key : 'K) (map : MapExt<'K, 'V>) = + let struct (l, _, _, _, r) = MapExt.splitV key map + struct (l, r) - member x.CompareTo (o : HalfRange) = - let c = x.Value.CompareTo o.Value - if c = 0 then - if x.IsMax = o.IsMax then 0 - else (if x.IsMax then 1 else -1) - else - c + let inline tryMinValue (map : MapExt<'K, 'V>) = + MapExt.tryMinV map |> ValueOption.map (fun mk -> map.[mk]) - interface IComparable with - member x.CompareTo o = - match o with - | :? HalfRange as o -> x.CompareTo(o) - | _ -> failwith "uncomparable" - end + let inline tryMaxValue (map : MapExt<'K, 'V>) = + MapExt.tryMaxV map |> ValueOption.map (fun mk -> map.[mk]) + let inline maxValue (map : MapExt<'K, 'V>) = + map.[MapExt.max map] -[] -type RangeSet = private { root : FingerTreeNode } with - - member private x.AsString = - x |> Seq.map (sprintf "%A") - |> String.concat "; " - |> sprintf "set [%s]" +open RangeSetUtils +/// Set of ranges where overlapping and neighboring ranges are coalesced. +/// Note that ranges describe closed intervals. +[] +type RangeSet1i internal (store : MapExt) = + static let empty = RangeSet1i(MapExt.empty) + + /// Empty range set. + static member Empty = empty + + /// Builds a range set of the given sequence of ranges. + static member OfSeq(ranges : Range1i seq) = + let arr = ranges |> Seq.toArray + if arr.Length = 0 then + empty + elif arr.Length = 1 then + let r = arr.[0] + RangeSet1i(MapExt.ofListV [ + struct (r.Min, HalfRangeKind.Left) + if r.Max < Int32.MaxValue then struct (r.Max + 1, HalfRangeKind.Right) + ]) + else + // TODO: better impl possible (sort array and traverse) + arr |> Array.fold (fun s r -> s.Add r) empty + + member inline private x.Store = store + + // We cannot directly describe a range that ends at Int32.MaxValue since the right half-range is inserted + // at max + 1. In that case the right-half range will be missing and the total count is odd. + member inline private x.HasMaxValue = store.Count % 2 = 1 + + /// Returns the minimum value in the range set or Int32.MaxValue if the range is empty. member x.Min = - match x.root |> FingerTreeNode.firstOpt with - | Some f -> f.Value + match store.TryMinKeyV with + | ValueSome min -> min | _ -> Int32.MaxValue + /// Returns the maximum value in the range set or Int32.MinValue if the range is empty. member x.Max = - match x.root |> FingerTreeNode.lastOpt with - | Some f -> f.Value - | _ -> Int32.MinValue + if x.HasMaxValue then Int32.MaxValue + else + match store.TryMaxKeyV with + | ValueSome max -> max - 1 + | _ -> Int32.MinValue + + /// Returns the total range spanned by the range set, i.e. [min, max]. + member inline x.Range = + Range1i(x.Min, x.Max) + + /// Adds the given range to the set. + member x.Add(r : Range1i) = + if r.Max < r.Min then + x + else + let min = r.Min + let struct (max, overflow) = inc r.Max + + let struct (lm, inner) = MapExt.splitAt min store + let rm = + if overflow then MapExt.empty + else sndv <| MapExt.splitAt max inner + + let before = MapExt.tryMaxValue lm + let after = MapExt.tryMinValue rm + + // If the set contained Int32.MaxValue or we have overflown, we must not add an explicit right half-range. + // Int32.MaxValue is stored implicitly. + let fixRightBoundary = + if x.HasMaxValue || overflow then + id + else + MapExt.add max HalfRangeKind.Right + + let newStore = + match before, after with + | ValueNone, ValueNone -> + MapExt.ofListV [ + struct (min, HalfRangeKind.Left) + ] + |> fixRightBoundary + + | ValueSome HalfRangeKind.Right, ValueNone -> + lm + |> MapExt.add min HalfRangeKind.Left + |> fixRightBoundary + + | ValueSome HalfRangeKind.Left, ValueNone -> + lm + |> fixRightBoundary + + | ValueNone, ValueSome HalfRangeKind.Left -> + rm + |> MapExt.add min HalfRangeKind.Left + |> MapExt.add max HalfRangeKind.Right + + | ValueNone, ValueSome HalfRangeKind.Right -> + rm + |> MapExt.add min HalfRangeKind.Left + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Left); struct (max, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (max, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Right -> + MapExt.union lm rm + + | _ -> + failwithf "impossible" + + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + RangeSet1i(newStore) + + /// Removes the given range from the set. + member x.Remove(r : Range1i) = + if r.Max < r.Min then + x + else + let min = r.Min + let struct (max, overflow) = inc r.Max + + let struct (lm, inner) = MapExt.splitAt min store + let rm = + if overflow then MapExt.empty + else sndv <| MapExt.splitAt max inner + + let before = MapExt.tryMaxValue lm + let after = MapExt.tryMinValue rm + + // If the set contained Int32.MaxValue and we have not overflown, there is still a range [max, Int32.MaxValue] + let fixRightBoundary = + if x.HasMaxValue && not overflow then + MapExt.add max HalfRangeKind.Left + else + id + + let newStore = + match before, after with + | ValueNone, ValueNone -> + MapExt.empty + |> fixRightBoundary + + | ValueSome HalfRangeKind.Right, ValueNone -> + lm + |> fixRightBoundary + + | ValueSome HalfRangeKind.Left, ValueNone -> + lm + |> MapExt.add min HalfRangeKind.Right + |> fixRightBoundary + + | ValueNone, ValueSome HalfRangeKind.Left -> + rm + + | ValueNone, ValueSome HalfRangeKind.Right -> + rm + |> MapExt.add max HalfRangeKind.Left + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Left -> + MapExt.union lm rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (max, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Right); struct (max, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm + + | _ -> + failwithf "impossible" + + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + RangeSet1i(newStore) + + /// Returns the intersection of the set with the given range. + member x.Intersect(r : Range1i) = + if r.Max < r.Min then + empty + else + let min = r.Min + let struct (max, overflow) = inc r.Max + + let inner = + store + |> MapExt.splitAt min |> sndv + |> if not overflow then MapExt.splitAt max >> fstv else id + + let newStore = + inner + |> if x.Contains r.Min then MapExt.add min HalfRangeKind.Left else id + |> if x.Contains r.Max && not overflow then MapExt.add max HalfRangeKind.Right else id + + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + RangeSet1i(newStore) + + /// Returns whether the given value is contained in the range set. + member x.Contains(v : int32) = + let struct (l, s, _) = MapExt.neighboursV v store + match s with + | ValueSome (_, k) -> k = HalfRangeKind.Left + | _ -> + match l with + | ValueSome (_, HalfRangeKind.Left) -> true + | _ -> false + + /// Returns the number of disjoint ranges in the set. + member x.Count = + (store.Count + 1) / 2 + + /// Returns whether the set is empty. + member inline x.IsEmpty = + x.Count = 0 + + /// Builds an array from the range set. + member x.ToArray() = + let arr = Array.zeroCreate x.Count + + let rec write (i : int) (l : struct (int32 * HalfRangeKind) list) = + match l with + | struct (l, _) :: struct (r, _) :: rest -> + arr.[i] <- Range1i(l, r - 1) + write (i + 1) rest + + | [struct (l, HalfRangeKind.Left)] when i = x.Count - 1 -> + arr.[i] <- Range1i(l, Int32.MaxValue) + + | [_] -> failwith "bad RangeSet" - member x.Range = - match FingerTreeNode.firstOpt x.root, FingerTreeNode.lastOpt x.root with - | Some min, Some max -> Range1i(min.Value, max.Value) - | _ -> Range1i.Invalid + | [] -> () + + store |> MapExt.toListV |> write 0 + arr + + /// Builds a list from the range set. + member x.ToList() = + let rec build (accum : Range1i list) (l : struct (int32 * HalfRangeKind) list) = + match l with + | struct (l, _) :: struct (r, _) :: rest -> + build (Range1i(l, r - 1) :: accum) rest + + | [struct (l, HalfRangeKind.Left)] -> + build (Range1i(l, Int32.MaxValue) :: accum) [] + + | [_] -> failwith "bad RangeSet" + + | [] -> List.rev accum + + store |> MapExt.toListV |> build [] + + /// Views the range set as a sequence. + member x.ToSeq() = + x :> seq<_> + + member inline private x.Equals(other : RangeSet1i) = + store = other.Store + + override x.Equals(other : obj) = + match other with + | :? RangeSet1i as o -> x.Equals o + | _ -> false + + override x.GetHashCode() = + store.GetHashCode() + + member private x.AsString = x.ToString() + + override x.ToString() = + let content = + x |> Seq.map (fun r -> + $"[{r.Min}, {r.Max}]" + ) + |> String.concat "; " + + $"ranges [{content}]" + + member x.GetEnumerator() = + new RangeSetEnumerator1i((store :> seq<_>).GetEnumerator()) + + interface IEquatable with + member x.Equals(other) = x.Equals(other) interface IEnumerable with - member x.GetEnumerator() = new RangeSetEnumerator(FingerTreeImplementation.FingerTreeNode.getEnumeratorFw x.root) :> IEnumerator + member x.GetEnumerator() = new RangeSetEnumerator1i((store :> seq<_>).GetEnumerator()) :> _ interface IEnumerable with - member x.GetEnumerator() = new RangeSetEnumerator(FingerTreeImplementation.FingerTreeNode.getEnumeratorFw x.root) :> _ + member x.GetEnumerator() = new RangeSetEnumerator1i((store :> seq<_>).GetEnumerator()) :> _ +// TODO: MapExt should use a struct enumerator and return it directly. +// That way we could get rid of allocations. +and RangeSetEnumerator1i = + struct + val private Inner : IEnumerator> + val mutable private Left : KeyValuePair + val mutable private Right : KeyValuePair -and private RangeSetEnumerator(i : IEnumerator) = - - let mutable last = HalfRange() - let mutable current = HalfRange() - - member x.Current = Range1i(last.Value, current.Value - 1) + internal new (inner : IEnumerator>) = + { Inner = inner + Left = Unchecked.defaultof<_> + Right = Unchecked.defaultof<_> } - interface IEnumerator with member x.MoveNext() = - if i.MoveNext() then - last <- i.Current - if i.MoveNext() then - current <- i.Current + if x.Inner.MoveNext() then + x.Left <- x.Inner.Current + if x.Inner.MoveNext() then + x.Right <- x.Inner.Current true else - false + if x.Left.Value = HalfRangeKind.Left then + x.Right <- KeyValuePair(Int32.MinValue, HalfRangeKind.Right) // MaxValue + 1 + true + else + failwithf "bad RangeSet" else false - member x.Current = x.Current :> obj - member x.Reset() = - i.Reset() - - interface IEnumerator with - member x.Current = x.Current - member x.Dispose() = i.Dispose() + x.Inner.Reset() + x.Left <- Unchecked.defaultof<_> + x.Right <- Unchecked.defaultof<_> + + member x.Current = + assert (x.Left.Value = HalfRangeKind.Left && x.Right.Value = HalfRangeKind.Right) + Range1i(x.Left.Key, x.Right.Key - 1) + + member x.Dispose() = + x.Inner.Dispose() + x.Left <- Unchecked.defaultof<_> + x.Right <- Unchecked.defaultof<_> + + interface IEnumerator with + member x.MoveNext() = x.MoveNext() + member x.Current = x.Current :> obj + member x.Reset() = x.Reset() + + interface IEnumerator with + member x.Dispose() = x.Dispose() + member x.Current = x.Current + end [] -module RangeSet = - let private mm = - { - quantify = fun (r : HalfRange) -> r - mempty = HalfRange(false, Int32.MinValue) - mappend = fun l r -> if l.CompareTo r > 0 then l else r - } +module RangeSet1i = - let private minRange = HalfRange(false, Int32.MinValue) + /// Empty range set. + let empty = RangeSet1i.Empty - let inline private leq v = HalfRange(true, v) - let inline private geq v = HalfRange(false, v) + /// Returns the minimum value in the range set or Int32.MaxValue if the range is empty. + let inline min (set : RangeSet1i) = set.Min - let inline private (|Leq|Geq|) (r : HalfRange) = - if r.IsMax then Leq r.Value - else Geq r.Value + /// Returns the maximum value in the range set or Int32.MinValue if the range is empty. + let inline max (set : RangeSet1i) = set.Max - let empty = { root = Empty } + /// Returns the total range spanned by the range set, i.e. [min, max]. + let inline range (set : RangeSet1i) = set.Range - let insert (range : Range1i) (t : RangeSet) = - let rangeMax = range.Max + 1 + /// Builds a range set of the given sequence of ranges. + let inline ofSeq (ranges : seq) = RangeSet1i.OfSeq ranges - let (l,rest) = t.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo range.Min >= 0) minRange - let (_,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax > 0) minRange + /// Builds a range set of the given list of ranges. + let inline ofList (ranges : Range1i list) = RangeSet1i.OfSeq ranges - let max = leq rangeMax - let min = geq range.Min + /// Builds a range set of the given array of ranges. + let inline ofArray (ranges : Range1i[]) = RangeSet1i.OfSeq ranges - match FingerTreeNode.lastOpt l, FingerTreeNode.firstOpt r with - | None, None -> - { root = Deep(max, One(min), Empty, One(max)) } + /// Adds the given range to the set. + let inline add (range : Range1i) (set : RangeSet1i) = set.Add range - | Some lmax, None -> - match lmax with - | Leq _ -> { root = l |> FingerTreeNode.append mm min |> FingerTreeNode.append mm max } - | Geq _ -> { root = l |> FingerTreeNode.append mm max } + [] + let inline insert (range : Range1i) (set : RangeSet1i) = set.Add range - | None, Some rmin -> - match rmin with - | Leq _ -> { root = r |> FingerTreeNode.prepend mm min } - | Geq _ -> { root = r |> FingerTreeNode.prepend mm max |> FingerTreeNode.prepend mm min } + /// Removes the given range from the set. + let inline remove (range : Range1i) (set : RangeSet1i) = set.Remove range - | Some lmax, Some rmin -> - match lmax, rmin with - | Leq _, Geq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [min;max] r } + /// Returns the intersection of the set with the given range. + let inline intersect (range : Range1i) (set : RangeSet1i) = set.Intersect range - | Geq _, Leq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [] r } + [] + let inline window (range : Range1i) (set : RangeSet1i) = intersect range set - | Leq _, Leq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [min] r } + /// Returns whether the given value is contained in the range set. + let inline contains (value : int32) (set : RangeSet1i) = set.Contains value - | Geq _, Geq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [max] r } + /// Returns the number of disjoint ranges in the set. + let inline count (set : RangeSet1i) = set.Count - let remove (range : Range1i) (t : RangeSet) = - let rangeMax = range.Max + 1 + /// Returns whether the set is empty. + let inline isEmpty (set : RangeSet1i) = set.IsEmpty - let (l,rest) = t.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo range.Min >= 0) minRange - let (_,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax > 0) minRange + /// Views the range set as a sequence. + let inline toSeq (set : RangeSet1i) = set :> seq<_> - let max = geq rangeMax - let min = leq range.Min + /// Builds a list from the range set. + let inline toList (set : RangeSet1i) = set.ToList() - match FingerTreeNode.lastOpt l, FingerTreeNode.firstOpt r with - | None, None -> - { root = Empty } + /// Builds an array from the range set. + let inline toArray (set : RangeSet1i) = set.ToArray() - | Some lmax, None -> - match lmax with - | Leq _ -> { root = l } - | Geq _ -> { root = l |> FingerTreeNode.append mm min } - | None, Some rmin -> - match rmin with - | Leq _ -> { root = r |> FingerTreeNode.prepend mm max } - | Geq _ -> { root = r } +/// Set of ranges where overlapping and neighboring ranges are coalesced. +/// Note that ranges describe closed intervals. +[] +type RangeSet1ui internal (store : MapExt) = + static let empty = RangeSet1ui(MapExt.empty) + + /// Empty range set. + static member Empty = empty + + /// Builds a range set of the given sequence of ranges. + static member OfSeq(ranges : Range1ui seq) = + let arr = ranges |> Seq.toArray + if arr.Length = 0 then + empty + elif arr.Length = 1 then + let r = arr.[0] + RangeSet1ui(MapExt.ofListV [ + struct (r.Min, HalfRangeKind.Left) + if r.Max < UInt32.MaxValue then struct (r.Max + 1u, HalfRangeKind.Right) + ]) + else + // TODO: better impl possible (sort array and traverse) + arr |> Array.fold (fun s r -> s.Add r) empty + + member inline private x.Store = store + + // We cannot directly describe a range that ends at UInt32.MaxValue since the right half-range is inserted + // at max + 1. In that case the right-half range will be missing and the total count is odd. + member inline private x.HasMaxValue = store.Count % 2 = 1 + + /// Returns the minimum value in the range set or UInt32.MaxValue if the range is empty. + member x.Min = + match store.TryMinKeyV with + | ValueSome min -> min + | _ -> UInt32.MaxValue - | Some lmax, Some rmin -> - match lmax, rmin with - | Leq _, Geq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [] r } + /// Returns the maximum value in the range set or UInt32.MinValue if the range is empty. + member x.Max = + if x.HasMaxValue then UInt32.MaxValue + else + match store.TryMaxKeyV with + | ValueSome max -> max - 1u + | _ -> UInt32.MinValue + + /// Returns the total range spanned by the range set, i.e. [min, max]. + member inline x.Range = + Range1ui(x.Min, x.Max) + + /// Adds the given range to the set. + member x.Add(r : Range1ui) = + if r.Max < r.Min then + x + else + let min = r.Min + let struct (max, overflow) = inc r.Max + + let struct (lm, inner) = MapExt.splitAt min store + let rm = + if overflow then MapExt.empty + else sndv <| MapExt.splitAt max inner + + let before = MapExt.tryMaxValue lm + let after = MapExt.tryMinValue rm + + // If the set contained UInt32.MaxValue or we have overflown, we must not add an explicit right half-range. + // UInt32.MaxValue is stored implicitly. + let fixRightBoundary = + if x.HasMaxValue || overflow then + id + else + MapExt.add max HalfRangeKind.Right + + let newStore = + match before, after with + | ValueNone, ValueNone -> + MapExt.ofListV [ + struct (min, HalfRangeKind.Left) + ] + |> fixRightBoundary + + | ValueSome HalfRangeKind.Right, ValueNone -> + lm + |> MapExt.add min HalfRangeKind.Left + |> fixRightBoundary + + | ValueSome HalfRangeKind.Left, ValueNone -> + lm + |> fixRightBoundary + + | ValueNone, ValueSome HalfRangeKind.Left -> + rm + |> MapExt.add min HalfRangeKind.Left + |> MapExt.add max HalfRangeKind.Right + + | ValueNone, ValueSome HalfRangeKind.Right -> + rm + |> MapExt.add min HalfRangeKind.Left + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Left); struct (max, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (max, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Right -> + MapExt.union lm rm + + | _ -> + failwithf "impossible" + + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + RangeSet1ui(newStore) + + /// Removes the given range from the set. + member x.Remove(r : Range1ui) = + if r.Max < r.Min then + x + else + let min = r.Min + let struct (max, overflow) = inc r.Max + + let struct (lm, inner) = MapExt.splitAt min store + let rm = + if overflow then MapExt.empty + else sndv <| MapExt.splitAt max inner + + let before = MapExt.tryMaxValue lm + let after = MapExt.tryMinValue rm + + // If the set contained UInt32.MaxValue and we have not overflown, there is still a range [max, UInt32.MaxValue] + let fixRightBoundary = + if x.HasMaxValue && not overflow then + MapExt.add max HalfRangeKind.Left + else + id + + let newStore = + match before, after with + | ValueNone, ValueNone -> + MapExt.empty + |> fixRightBoundary + + | ValueSome HalfRangeKind.Right, ValueNone -> + lm + |> fixRightBoundary + + | ValueSome HalfRangeKind.Left, ValueNone -> + lm + |> MapExt.add min HalfRangeKind.Right + |> fixRightBoundary + + | ValueNone, ValueSome HalfRangeKind.Left -> + rm - | Geq _, Leq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [min; max] r } + | ValueNone, ValueSome HalfRangeKind.Right -> + rm + |> MapExt.add max HalfRangeKind.Left - | Leq _, Leq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [max] r } + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Left -> + MapExt.union lm rm - | Geq _, Geq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [min] r } + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm - let ofSeq (s : seq) = - let mutable res = empty - for e in s do res <- insert e res - res + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (max, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm - let inline ofList (l : list) = ofSeq l - let inline ofArray (l : Range1i[]) = ofSeq l + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Right); struct (max, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm - let toSeq (r : RangeSet) = r :> seq<_> - let toList (r : RangeSet) = r |> Seq.toList - let toArray (r : RangeSet) = r |> Seq.toArray + | _ -> + failwithf "impossible" - let inline min (t : RangeSet) = t.Min - let inline max (t : RangeSet) = t.Max - let inline range (t : RangeSet) = t.Range + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + RangeSet1ui(newStore) - let window (window : Range1i) (set : RangeSet) = - let rangeMax = window.Max + 1 + /// Returns the intersection of the set with the given range. + member x.Intersect(r : Range1ui) = + if r.Max < r.Min then + empty + else + let min = r.Min + let struct (max, overflow) = inc r.Max - let (l,rest) = set.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo window.Min > 0) minRange - let (inner,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax >= 0) minRange + let inner = + store + |> MapExt.splitAt min |> sndv + |> if not overflow then MapExt.splitAt max >> fstv else id - let inner = - match FingerTreeNode.lastOpt l with - | Some (Geq _) -> FingerTreeNode.prepend mm (geq window.Min) inner - | _ -> inner + let newStore = + inner + |> if x.Contains r.Min then MapExt.add min HalfRangeKind.Left else id + |> if x.Contains r.Max && not overflow then MapExt.add max HalfRangeKind.Right else id - let inner = - match FingerTreeNode.firstOpt r with - | Some (Leq _) -> FingerTreeNode.append mm (leq rangeMax) inner - | _ -> inner + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + RangeSet1ui(newStore) - { root = inner } + /// Returns whether the given value is contained in the range set. + member x.Contains(v : uint32) = + let struct (l, s, _) = MapExt.neighboursV v store + match s with + | ValueSome (_, k) -> k = HalfRangeKind.Left + | _ -> + match l with + | ValueSome (_, HalfRangeKind.Left) -> true + | _ -> false -[] -type private HalfRange64 = - struct - val mutable public IsMax : bool - val mutable public Value : int64 + /// Returns the number of disjoint ranges in the set. + member x.Count = + (store.Count + 1) / 2 - new(m,v) = { IsMax = m; Value = v } + /// Returns whether the set is empty. + member inline x.IsEmpty = + x.Count = 0 - override x.GetHashCode() = - if x.IsMax then 0 - else x.Value.GetHashCode() + /// Builds an array from the range set. + member x.ToArray() = + let arr = Array.zeroCreate x.Count - override x.Equals o = - match o with - | :? HalfRange64 as o -> - x.IsMax = o.IsMax && x.Value = o.Value - | _ -> - false + let rec write (i : int) (l : struct (uint32 * HalfRangeKind) list) = + match l with + | struct (l, _) :: struct (r, _) :: rest -> + arr.[i] <- Range1ui(l, r - 1u) + write (i + 1) rest + + | [struct (l, HalfRangeKind.Left)] when i = x.Count - 1 -> + arr.[i] <- Range1ui(l, UInt32.MaxValue) + + | [_] -> failwith "bad RangeSet" + + | [] -> () + + store |> MapExt.toListV |> write 0 + arr + + /// Builds a list from the range set. + member x.ToList() = + let rec build (accum : Range1ui list) (l : struct (uint32 * HalfRangeKind) list) = + match l with + | struct (l, _) :: struct (r, _) :: rest -> + build (Range1ui(l, r - 1u) :: accum) rest + + | [struct (l, HalfRangeKind.Left)] -> + build (Range1ui(l, UInt32.MaxValue) :: accum) [] + + | [_] -> failwith "bad RangeSet" + + | [] -> List.rev accum + + store |> MapExt.toListV |> build [] + + /// Views the range set as a sequence. + member x.ToSeq() = + x :> seq<_> + + member inline private x.Equals(other : RangeSet1ui) = + store = other.Store + + override x.Equals(other : obj) = + match other with + | :? RangeSet1ui as o -> x.Equals o + | _ -> false - member x.CompareTo (o : HalfRange64) = - let c = x.Value.CompareTo o.Value - if c = 0 then - if x.IsMax = o.IsMax then 0 - else (if x.IsMax then 1 else -1) + override x.GetHashCode() = + store.GetHashCode() + + member private x.AsString = x.ToString() + + override x.ToString() = + let content = + x |> Seq.map (fun r -> + $"[{r.Min}, {r.Max}]" + ) + |> String.concat "; " + + $"ranges [{content}]" + + member x.GetEnumerator() = + new RangeSetEnumerator1ui((store :> seq<_>).GetEnumerator()) + + interface IEquatable with + member x.Equals(other) = x.Equals(other) + + interface IEnumerable with + member x.GetEnumerator() = new RangeSetEnumerator1ui((store :> seq<_>).GetEnumerator()) :> _ + + interface IEnumerable with + member x.GetEnumerator() = new RangeSetEnumerator1ui((store :> seq<_>).GetEnumerator()) :> _ + +// TODO: MapExt should use a struct enumerator and return it directly. +// That way we could get rid of allocations. +and RangeSetEnumerator1ui = + struct + val private Inner : IEnumerator> + val mutable private Left : KeyValuePair + val mutable private Right : KeyValuePair + + internal new (inner : IEnumerator>) = + { Inner = inner + Left = Unchecked.defaultof<_> + Right = Unchecked.defaultof<_> } + + member x.MoveNext() = + if x.Inner.MoveNext() then + x.Left <- x.Inner.Current + if x.Inner.MoveNext() then + x.Right <- x.Inner.Current + true + else + if x.Left.Value = HalfRangeKind.Left then + x.Right <- KeyValuePair(UInt32.MinValue, HalfRangeKind.Right) // MaxValue + 1 + true + else + failwithf "bad RangeSet" else - c + false - interface IComparable with - member x.CompareTo o = - match o with - | :? HalfRange64 as o -> x.CompareTo(o) - | _ -> failwith "uncomparable" + member x.Reset() = + x.Inner.Reset() + x.Left <- Unchecked.defaultof<_> + x.Right <- Unchecked.defaultof<_> + + member x.Current = + assert (x.Left.Value = HalfRangeKind.Left && x.Right.Value = HalfRangeKind.Right) + Range1ui(x.Left.Key, x.Right.Key - 1u) + + member x.Dispose() = + x.Inner.Dispose() + x.Left <- Unchecked.defaultof<_> + x.Right <- Unchecked.defaultof<_> + + interface IEnumerator with + member x.MoveNext() = x.MoveNext() + member x.Current = x.Current :> obj + member x.Reset() = x.Reset() + + interface IEnumerator with + member x.Dispose() = x.Dispose() + member x.Current = x.Current end +[] +module RangeSet1ui = -[] -type RangeSet64 = private { root : FingerTreeNode } with - - member private x.AsString = - x |> Seq.map (sprintf "%A") - |> String.concat "; " - |> sprintf "set [%s]" + /// Empty range set. + let empty = RangeSet1ui.Empty + + /// Returns the minimum value in the range set or UInt32.MaxValue if the range is empty. + let inline min (set : RangeSet1ui) = set.Min + + /// Returns the maximum value in the range set or UInt32.MinValue if the range is empty. + let inline max (set : RangeSet1ui) = set.Max + + /// Returns the total range spanned by the range set, i.e. [min, max]. + let inline range (set : RangeSet1ui) = set.Range + + /// Builds a range set of the given sequence of ranges. + let inline ofSeq (ranges : seq) = RangeSet1ui.OfSeq ranges + + /// Builds a range set of the given list of ranges. + let inline ofList (ranges : Range1ui list) = RangeSet1ui.OfSeq ranges + + /// Builds a range set of the given array of ranges. + let inline ofArray (ranges : Range1ui[]) = RangeSet1ui.OfSeq ranges + + /// Adds the given range to the set. + let inline add (range : Range1ui) (set : RangeSet1ui) = set.Add range + [] + let inline insert (range : Range1ui) (set : RangeSet1ui) = set.Add range + + /// Removes the given range from the set. + let inline remove (range : Range1ui) (set : RangeSet1ui) = set.Remove range + + /// Returns the intersection of the set with the given range. + let inline intersect (range : Range1ui) (set : RangeSet1ui) = set.Intersect range + + [] + let inline window (range : Range1ui) (set : RangeSet1ui) = intersect range set + + /// Returns whether the given value is contained in the range set. + let inline contains (value : uint32) (set : RangeSet1ui) = set.Contains value + + /// Returns the number of disjoint ranges in the set. + let inline count (set : RangeSet1ui) = set.Count + + /// Returns whether the set is empty. + let inline isEmpty (set : RangeSet1ui) = set.IsEmpty + + /// Views the range set as a sequence. + let inline toSeq (set : RangeSet1ui) = set :> seq<_> + + /// Builds a list from the range set. + let inline toList (set : RangeSet1ui) = set.ToList() + + /// Builds an array from the range set. + let inline toArray (set : RangeSet1ui) = set.ToArray() + + +/// Set of ranges where overlapping and neighboring ranges are coalesced. +/// Note that ranges describe closed intervals. +[] +type RangeSet1l internal (store : MapExt) = + static let empty = RangeSet1l(MapExt.empty) + + /// Empty range set. + static member Empty = empty + + /// Builds a range set of the given sequence of ranges. + static member OfSeq(ranges : Range1l seq) = + let arr = ranges |> Seq.toArray + if arr.Length = 0 then + empty + elif arr.Length = 1 then + let r = arr.[0] + RangeSet1l(MapExt.ofListV [ + struct (r.Min, HalfRangeKind.Left) + if r.Max < Int64.MaxValue then struct (r.Max + 1L, HalfRangeKind.Right) + ]) + else + // TODO: better impl possible (sort array and traverse) + arr |> Array.fold (fun s r -> s.Add r) empty + + member inline private x.Store = store + + // We cannot directly describe a range that ends at Int64.MaxValue since the right half-range is inserted + // at max + 1. In that case the right-half range will be missing and the total count is odd. + member inline private x.HasMaxValue = store.Count % 2 = 1 + + /// Returns the minimum value in the range set or Int64.MaxValue if the range is empty. member x.Min = - match x.root |> FingerTreeNode.firstOpt with - | Some f -> f.Value + match store.TryMinKeyV with + | ValueSome min -> min | _ -> Int64.MaxValue + /// Returns the maximum value in the range set or Int64.MinValue if the range is empty. member x.Max = - match x.root |> FingerTreeNode.lastOpt with - | Some f -> f.Value - | _ -> Int64.MinValue + if x.HasMaxValue then Int64.MaxValue + else + match store.TryMaxKeyV with + | ValueSome max -> max - 1L + | _ -> Int64.MinValue + + /// Returns the total range spanned by the range set, i.e. [min, max]. + member inline x.Range = + Range1l(x.Min, x.Max) + + /// Adds the given range to the set. + member x.Add(r : Range1l) = + if r.Max < r.Min then + x + else + let min = r.Min + let struct (max, overflow) = inc r.Max + + let struct (lm, inner) = MapExt.splitAt min store + let rm = + if overflow then MapExt.empty + else sndv <| MapExt.splitAt max inner + + let before = MapExt.tryMaxValue lm + let after = MapExt.tryMinValue rm + + // If the set contained Int64.MaxValue or we have overflown, we must not add an explicit right half-range. + // Int64.MaxValue is stored implicitly. + let fixRightBoundary = + if x.HasMaxValue || overflow then + id + else + MapExt.add max HalfRangeKind.Right + + let newStore = + match before, after with + | ValueNone, ValueNone -> + MapExt.ofListV [ + struct (min, HalfRangeKind.Left) + ] + |> fixRightBoundary + + | ValueSome HalfRangeKind.Right, ValueNone -> + lm + |> MapExt.add min HalfRangeKind.Left + |> fixRightBoundary + + | ValueSome HalfRangeKind.Left, ValueNone -> + lm + |> fixRightBoundary + + | ValueNone, ValueSome HalfRangeKind.Left -> + rm + |> MapExt.add min HalfRangeKind.Left + |> MapExt.add max HalfRangeKind.Right + + | ValueNone, ValueSome HalfRangeKind.Right -> + rm + |> MapExt.add min HalfRangeKind.Left + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Left); struct (max, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (max, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Right -> + MapExt.union lm rm + + | _ -> + failwithf "impossible" + + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + RangeSet1l(newStore) + + /// Removes the given range from the set. + member x.Remove(r : Range1l) = + if r.Max < r.Min then + x + else + let min = r.Min + let struct (max, overflow) = inc r.Max + + let struct (lm, inner) = MapExt.splitAt min store + let rm = + if overflow then MapExt.empty + else sndv <| MapExt.splitAt max inner + + let before = MapExt.tryMaxValue lm + let after = MapExt.tryMinValue rm + + // If the set contained Int64.MaxValue and we have not overflown, there is still a range [max, Int64.MaxValue] + let fixRightBoundary = + if x.HasMaxValue && not overflow then + MapExt.add max HalfRangeKind.Left + else + id + + let newStore = + match before, after with + | ValueNone, ValueNone -> + MapExt.empty + |> fixRightBoundary + + | ValueSome HalfRangeKind.Right, ValueNone -> + lm + |> fixRightBoundary + + | ValueSome HalfRangeKind.Left, ValueNone -> + lm + |> MapExt.add min HalfRangeKind.Right + |> fixRightBoundary + + | ValueNone, ValueSome HalfRangeKind.Left -> + rm + + | ValueNone, ValueSome HalfRangeKind.Right -> + rm + |> MapExt.add max HalfRangeKind.Left + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Left -> + MapExt.union lm rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (max, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Right); struct (max, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm + + | _ -> + failwithf "impossible" + + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + RangeSet1l(newStore) + + /// Returns the intersection of the set with the given range. + member x.Intersect(r : Range1l) = + if r.Max < r.Min then + empty + else + let min = r.Min + let struct (max, overflow) = inc r.Max + + let inner = + store + |> MapExt.splitAt min |> sndv + |> if not overflow then MapExt.splitAt max >> fstv else id + + let newStore = + inner + |> if x.Contains r.Min then MapExt.add min HalfRangeKind.Left else id + |> if x.Contains r.Max && not overflow then MapExt.add max HalfRangeKind.Right else id + + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + RangeSet1l(newStore) + + /// Returns whether the given value is contained in the range set. + member x.Contains(v : int64) = + let struct (l, s, _) = MapExt.neighboursV v store + match s with + | ValueSome (_, k) -> k = HalfRangeKind.Left + | _ -> + match l with + | ValueSome (_, HalfRangeKind.Left) -> true + | _ -> false + + /// Returns the number of disjoint ranges in the set. + member x.Count = + (store.Count + 1) / 2 + + /// Returns whether the set is empty. + member inline x.IsEmpty = + x.Count = 0 + + /// Builds an array from the range set. + member x.ToArray() = + let arr = Array.zeroCreate x.Count + + let rec write (i : int) (l : struct (int64 * HalfRangeKind) list) = + match l with + | struct (l, _) :: struct (r, _) :: rest -> + arr.[i] <- Range1l(l, r - 1L) + write (i + 1) rest + + | [struct (l, HalfRangeKind.Left)] when i = x.Count - 1 -> + arr.[i] <- Range1l(l, Int64.MaxValue) + + | [_] -> failwith "bad RangeSet" + + | [] -> () + + store |> MapExt.toListV |> write 0 + arr + + /// Builds a list from the range set. + member x.ToList() = + let rec build (accum : Range1l list) (l : struct (int64 * HalfRangeKind) list) = + match l with + | struct (l, _) :: struct (r, _) :: rest -> + build (Range1l(l, r - 1L) :: accum) rest + + | [struct (l, HalfRangeKind.Left)] -> + build (Range1l(l, Int64.MaxValue) :: accum) [] + + | [_] -> failwith "bad RangeSet" + + | [] -> List.rev accum - member x.Range = - match FingerTreeNode.firstOpt x.root, FingerTreeNode.lastOpt x.root with - | Some min, Some max -> Range1l(min.Value, max.Value) - | _ -> Range1l.Invalid + store |> MapExt.toListV |> build [] + + /// Views the range set as a sequence. + member x.ToSeq() = + x :> seq<_> + + member inline private x.Equals(other : RangeSet1l) = + store = other.Store + + override x.Equals(other : obj) = + match other with + | :? RangeSet1l as o -> x.Equals o + | _ -> false + + override x.GetHashCode() = + store.GetHashCode() + + member private x.AsString = x.ToString() + + override x.ToString() = + let content = + x |> Seq.map (fun r -> + $"[{r.Min}, {r.Max}]" + ) + |> String.concat "; " + + $"ranges [{content}]" + + member x.GetEnumerator() = + new RangeSetEnumerator1l((store :> seq<_>).GetEnumerator()) + + interface IEquatable with + member x.Equals(other) = x.Equals(other) interface IEnumerable with - member x.GetEnumerator() = new RangeSet64Enumerator(FingerTreeImplementation.FingerTreeNode.getEnumeratorFw x.root) :> IEnumerator + member x.GetEnumerator() = new RangeSetEnumerator1l((store :> seq<_>).GetEnumerator()) :> _ interface IEnumerable with - member x.GetEnumerator() = new RangeSet64Enumerator(FingerTreeImplementation.FingerTreeNode.getEnumeratorFw x.root) :> _ - + member x.GetEnumerator() = new RangeSetEnumerator1l((store :> seq<_>).GetEnumerator()) :> _ -and private RangeSet64Enumerator(i : IEnumerator) = - - let mutable last = HalfRange64() - let mutable current = HalfRange64() +// TODO: MapExt should use a struct enumerator and return it directly. +// That way we could get rid of allocations. +and RangeSetEnumerator1l = + struct + val private Inner : IEnumerator> + val mutable private Left : KeyValuePair + val mutable private Right : KeyValuePair - member x.Current = Range1l(last.Value, current.Value - 1L) + internal new (inner : IEnumerator>) = + { Inner = inner + Left = Unchecked.defaultof<_> + Right = Unchecked.defaultof<_> } - interface IEnumerator with member x.MoveNext() = - if i.MoveNext() then - last <- i.Current - if i.MoveNext() then - current <- i.Current + if x.Inner.MoveNext() then + x.Left <- x.Inner.Current + if x.Inner.MoveNext() then + x.Right <- x.Inner.Current true else - false + if x.Left.Value = HalfRangeKind.Left then + x.Right <- KeyValuePair(Int64.MinValue, HalfRangeKind.Right) // MaxValue + 1 + true + else + failwithf "bad RangeSet" else false - member x.Current = x.Current :> obj - member x.Reset() = - i.Reset() - - interface IEnumerator with - member x.Current = x.Current - member x.Dispose() = i.Dispose() + x.Inner.Reset() + x.Left <- Unchecked.defaultof<_> + x.Right <- Unchecked.defaultof<_> + + member x.Current = + assert (x.Left.Value = HalfRangeKind.Left && x.Right.Value = HalfRangeKind.Right) + Range1l(x.Left.Key, x.Right.Key - 1L) + + member x.Dispose() = + x.Inner.Dispose() + x.Left <- Unchecked.defaultof<_> + x.Right <- Unchecked.defaultof<_> + + interface IEnumerator with + member x.MoveNext() = x.MoveNext() + member x.Current = x.Current :> obj + member x.Reset() = x.Reset() + + interface IEnumerator with + member x.Dispose() = x.Dispose() + member x.Current = x.Current + end [] -module RangeSet64 = - let private mm = - { - quantify = fun (r : HalfRange64) -> r - mempty = HalfRange64(false, Int64.MinValue) - mappend = fun l r -> if l.CompareTo r > 0 then l else r - } +module RangeSet1l = + + /// Empty range set. + let empty = RangeSet1l.Empty + + /// Returns the minimum value in the range set or Int64.MaxValue if the range is empty. + let inline min (set : RangeSet1l) = set.Min + + /// Returns the maximum value in the range set or Int64.MinValue if the range is empty. + let inline max (set : RangeSet1l) = set.Max + + /// Returns the total range spanned by the range set, i.e. [min, max]. + let inline range (set : RangeSet1l) = set.Range + + /// Builds a range set of the given sequence of ranges. + let inline ofSeq (ranges : seq) = RangeSet1l.OfSeq ranges + + /// Builds a range set of the given list of ranges. + let inline ofList (ranges : Range1l list) = RangeSet1l.OfSeq ranges - let private minRange = HalfRange64(false, Int64.MinValue) + /// Builds a range set of the given array of ranges. + let inline ofArray (ranges : Range1l[]) = RangeSet1l.OfSeq ranges - let inline private leq v = HalfRange64(true, v) - let inline private geq v = HalfRange64(false, v) + /// Adds the given range to the set. + let inline add (range : Range1l) (set : RangeSet1l) = set.Add range - let inline private (|Leq|Geq|) (r : HalfRange64) = - if r.IsMax then Leq r.Value - else Geq r.Value + [] + let inline insert (range : Range1l) (set : RangeSet1l) = set.Add range - let empty = { root = Empty } + /// Removes the given range from the set. + let inline remove (range : Range1l) (set : RangeSet1l) = set.Remove range - let insert (range : Range1l) (t : RangeSet64) = - let rangeMax = range.Max + 1L + /// Returns the intersection of the set with the given range. + let inline intersect (range : Range1l) (set : RangeSet1l) = set.Intersect range - let (l,rest) = t.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo range.Min >= 0) minRange - let (_,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax > 0) minRange + [] + let inline window (range : Range1l) (set : RangeSet1l) = intersect range set - let max = leq rangeMax - let min = geq range.Min + /// Returns whether the given value is contained in the range set. + let inline contains (value : int64) (set : RangeSet1l) = set.Contains value - match FingerTreeNode.lastOpt l, FingerTreeNode.firstOpt r with - | None, None -> - { root = Deep(max, One(min), Empty, One(max)) } + /// Returns the number of disjoint ranges in the set. + let inline count (set : RangeSet1l) = set.Count - | Some lmax, None -> - match lmax with - | Leq _ -> { root = l |> FingerTreeNode.append mm min |> FingerTreeNode.append mm max } - | Geq _ -> { root = l |> FingerTreeNode.append mm max } + /// Returns whether the set is empty. + let inline isEmpty (set : RangeSet1l) = set.IsEmpty - | None, Some rmin -> - match rmin with - | Leq _ -> { root = r |> FingerTreeNode.prepend mm min } - | Geq _ -> { root = r |> FingerTreeNode.prepend mm max |> FingerTreeNode.prepend mm min } + /// Views the range set as a sequence. + let inline toSeq (set : RangeSet1l) = set :> seq<_> - | Some lmax, Some rmin -> - match lmax, rmin with - | Leq _, Geq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [min;max] r } + /// Builds a list from the range set. + let inline toList (set : RangeSet1l) = set.ToList() - | Geq _, Leq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [] r } + /// Builds an array from the range set. + let inline toArray (set : RangeSet1l) = set.ToArray() - | Leq _, Leq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [min] r } - | Geq _, Geq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [max] r } +/// Set of ranges where overlapping and neighboring ranges are coalesced. +/// Note that ranges describe closed intervals. +[] +type RangeSet1ul internal (store : MapExt) = + static let empty = RangeSet1ul(MapExt.empty) + + /// Empty range set. + static member Empty = empty + + /// Builds a range set of the given sequence of ranges. + static member OfSeq(ranges : Range1ul seq) = + let arr = ranges |> Seq.toArray + if arr.Length = 0 then + empty + elif arr.Length = 1 then + let r = arr.[0] + RangeSet1ul(MapExt.ofListV [ + struct (r.Min, HalfRangeKind.Left) + if r.Max < UInt64.MaxValue then struct (r.Max + 1UL, HalfRangeKind.Right) + ]) + else + // TODO: better impl possible (sort array and traverse) + arr |> Array.fold (fun s r -> s.Add r) empty + + member inline private x.Store = store + + // We cannot directly describe a range that ends at UInt64.MaxValue since the right half-range is inserted + // at max + 1. In that case the right-half range will be missing and the total count is odd. + member inline private x.HasMaxValue = store.Count % 2 = 1 + + /// Returns the minimum value in the range set or UInt64.MaxValue if the range is empty. + member x.Min = + match store.TryMinKeyV with + | ValueSome min -> min + | _ -> UInt64.MaxValue + + /// Returns the maximum value in the range set or UInt64.MinValue if the range is empty. + member x.Max = + if x.HasMaxValue then UInt64.MaxValue + else + match store.TryMaxKeyV with + | ValueSome max -> max - 1UL + | _ -> UInt64.MinValue + + /// Returns the total range spanned by the range set, i.e. [min, max]. + member inline x.Range = + Range1ul(x.Min, x.Max) + + /// Adds the given range to the set. + member x.Add(r : Range1ul) = + if r.Max < r.Min then + x + else + let min = r.Min + let struct (max, overflow) = inc r.Max + + let struct (lm, inner) = MapExt.splitAt min store + let rm = + if overflow then MapExt.empty + else sndv <| MapExt.splitAt max inner + + let before = MapExt.tryMaxValue lm + let after = MapExt.tryMinValue rm + + // If the set contained UInt64.MaxValue or we have overflown, we must not add an explicit right half-range. + // UInt64.MaxValue is stored implicitly. + let fixRightBoundary = + if x.HasMaxValue || overflow then + id + else + MapExt.add max HalfRangeKind.Right + + let newStore = + match before, after with + | ValueNone, ValueNone -> + MapExt.ofListV [ + struct (min, HalfRangeKind.Left) + ] + |> fixRightBoundary + + | ValueSome HalfRangeKind.Right, ValueNone -> + lm + |> MapExt.add min HalfRangeKind.Left + |> fixRightBoundary + + | ValueSome HalfRangeKind.Left, ValueNone -> + lm + |> fixRightBoundary + + | ValueNone, ValueSome HalfRangeKind.Left -> + rm + |> MapExt.add min HalfRangeKind.Left + |> MapExt.add max HalfRangeKind.Right + + | ValueNone, ValueSome HalfRangeKind.Right -> + rm + |> MapExt.add min HalfRangeKind.Left + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Left); struct (max, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (max, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Right -> + MapExt.union lm rm + + | _ -> + failwithf "impossible" + + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + RangeSet1ul(newStore) + + /// Removes the given range from the set. + member x.Remove(r : Range1ul) = + if r.Max < r.Min then + x + else + let min = r.Min + let struct (max, overflow) = inc r.Max + + let struct (lm, inner) = MapExt.splitAt min store + let rm = + if overflow then MapExt.empty + else sndv <| MapExt.splitAt max inner + + let before = MapExt.tryMaxValue lm + let after = MapExt.tryMinValue rm + + // If the set contained UInt64.MaxValue and we have not overflown, there is still a range [max, UInt64.MaxValue] + let fixRightBoundary = + if x.HasMaxValue && not overflow then + MapExt.add max HalfRangeKind.Left + else + id + + let newStore = + match before, after with + | ValueNone, ValueNone -> + MapExt.empty + |> fixRightBoundary + + | ValueSome HalfRangeKind.Right, ValueNone -> + lm + |> fixRightBoundary + + | ValueSome HalfRangeKind.Left, ValueNone -> + lm + |> MapExt.add min HalfRangeKind.Right + |> fixRightBoundary + + | ValueNone, ValueSome HalfRangeKind.Left -> + rm + + | ValueNone, ValueSome HalfRangeKind.Right -> + rm + |> MapExt.add max HalfRangeKind.Left + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Left -> + MapExt.union lm rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (max, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Right); struct (max, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm + + | _ -> + failwithf "impossible" + + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + RangeSet1ul(newStore) + + /// Returns the intersection of the set with the given range. + member x.Intersect(r : Range1ul) = + if r.Max < r.Min then + empty + else + let min = r.Min + let struct (max, overflow) = inc r.Max + + let inner = + store + |> MapExt.splitAt min |> sndv + |> if not overflow then MapExt.splitAt max >> fstv else id + + let newStore = + inner + |> if x.Contains r.Min then MapExt.add min HalfRangeKind.Left else id + |> if x.Contains r.Max && not overflow then MapExt.add max HalfRangeKind.Right else id + + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + RangeSet1ul(newStore) + + /// Returns whether the given value is contained in the range set. + member x.Contains(v : uint64) = + let struct (l, s, _) = MapExt.neighboursV v store + match s with + | ValueSome (_, k) -> k = HalfRangeKind.Left + | _ -> + match l with + | ValueSome (_, HalfRangeKind.Left) -> true + | _ -> false + + /// Returns the number of disjoint ranges in the set. + member x.Count = + (store.Count + 1) / 2 + + /// Returns whether the set is empty. + member inline x.IsEmpty = + x.Count = 0 + + /// Builds an array from the range set. + member x.ToArray() = + let arr = Array.zeroCreate x.Count + + let rec write (i : int) (l : struct (uint64 * HalfRangeKind) list) = + match l with + | struct (l, _) :: struct (r, _) :: rest -> + arr.[i] <- Range1ul(l, r - 1UL) + write (i + 1) rest + + | [struct (l, HalfRangeKind.Left)] when i = x.Count - 1 -> + arr.[i] <- Range1ul(l, UInt64.MaxValue) + + | [_] -> failwith "bad RangeSet" + + | [] -> () + + store |> MapExt.toListV |> write 0 + arr + + /// Builds a list from the range set. + member x.ToList() = + let rec build (accum : Range1ul list) (l : struct (uint64 * HalfRangeKind) list) = + match l with + | struct (l, _) :: struct (r, _) :: rest -> + build (Range1ul(l, r - 1UL) :: accum) rest + + | [struct (l, HalfRangeKind.Left)] -> + build (Range1ul(l, UInt64.MaxValue) :: accum) [] + + | [_] -> failwith "bad RangeSet" + + | [] -> List.rev accum + + store |> MapExt.toListV |> build [] + + /// Views the range set as a sequence. + member x.ToSeq() = + x :> seq<_> + + member inline private x.Equals(other : RangeSet1ul) = + store = other.Store + + override x.Equals(other : obj) = + match other with + | :? RangeSet1ul as o -> x.Equals o + | _ -> false + + override x.GetHashCode() = + store.GetHashCode() + + member private x.AsString = x.ToString() + + override x.ToString() = + let content = + x |> Seq.map (fun r -> + $"[{r.Min}, {r.Max}]" + ) + |> String.concat "; " + + $"ranges [{content}]" + + member x.GetEnumerator() = + new RangeSetEnumerator1ul((store :> seq<_>).GetEnumerator()) + + interface IEquatable with + member x.Equals(other) = x.Equals(other) + + interface IEnumerable with + member x.GetEnumerator() = new RangeSetEnumerator1ul((store :> seq<_>).GetEnumerator()) :> _ + + interface IEnumerable with + member x.GetEnumerator() = new RangeSetEnumerator1ul((store :> seq<_>).GetEnumerator()) :> _ + +// TODO: MapExt should use a struct enumerator and return it directly. +// That way we could get rid of allocations. +and RangeSetEnumerator1ul = + struct + val private Inner : IEnumerator> + val mutable private Left : KeyValuePair + val mutable private Right : KeyValuePair + + internal new (inner : IEnumerator>) = + { Inner = inner + Left = Unchecked.defaultof<_> + Right = Unchecked.defaultof<_> } + + member x.MoveNext() = + if x.Inner.MoveNext() then + x.Left <- x.Inner.Current + if x.Inner.MoveNext() then + x.Right <- x.Inner.Current + true + else + if x.Left.Value = HalfRangeKind.Left then + x.Right <- KeyValuePair(UInt64.MinValue, HalfRangeKind.Right) // MaxValue + 1 + true + else + failwithf "bad RangeSet" + else + false + + member x.Reset() = + x.Inner.Reset() + x.Left <- Unchecked.defaultof<_> + x.Right <- Unchecked.defaultof<_> + + member x.Current = + assert (x.Left.Value = HalfRangeKind.Left && x.Right.Value = HalfRangeKind.Right) + Range1ul(x.Left.Key, x.Right.Key - 1UL) + + member x.Dispose() = + x.Inner.Dispose() + x.Left <- Unchecked.defaultof<_> + x.Right <- Unchecked.defaultof<_> + + interface IEnumerator with + member x.MoveNext() = x.MoveNext() + member x.Current = x.Current :> obj + member x.Reset() = x.Reset() + + interface IEnumerator with + member x.Dispose() = x.Dispose() + member x.Current = x.Current + end + +[] +module RangeSet1ul = - let remove (range : Range1l) (t : RangeSet64) = - let rangeMax = range.Max + 1L + /// Empty range set. + let empty = RangeSet1ul.Empty - let (l,rest) = t.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo range.Min >= 0) minRange - let (_,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax > 0) minRange + /// Returns the minimum value in the range set or UInt64.MaxValue if the range is empty. + let inline min (set : RangeSet1ul) = set.Min - let max = geq rangeMax - let min = leq range.Min + /// Returns the maximum value in the range set or UInt64.MinValue if the range is empty. + let inline max (set : RangeSet1ul) = set.Max - match FingerTreeNode.lastOpt l, FingerTreeNode.firstOpt r with - | None, None -> - { root = Empty } + /// Returns the total range spanned by the range set, i.e. [min, max]. + let inline range (set : RangeSet1ul) = set.Range - | Some lmax, None -> - match lmax with - | Leq _ -> { root = l } - | Geq _ -> { root = l |> FingerTreeNode.append mm min } + /// Builds a range set of the given sequence of ranges. + let inline ofSeq (ranges : seq) = RangeSet1ul.OfSeq ranges - | None, Some rmin -> - match rmin with - | Leq _ -> { root = r |> FingerTreeNode.prepend mm max } - | Geq _ -> { root = r } + /// Builds a range set of the given list of ranges. + let inline ofList (ranges : Range1ul list) = RangeSet1ul.OfSeq ranges - | Some lmax, Some rmin -> - match lmax, rmin with - | Leq _, Geq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [] r } + /// Builds a range set of the given array of ranges. + let inline ofArray (ranges : Range1ul[]) = RangeSet1ul.OfSeq ranges - | Geq _, Leq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [min; max] r } + /// Adds the given range to the set. + let inline add (range : Range1ul) (set : RangeSet1ul) = set.Add range - | Leq _, Leq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [max] r } + [] + let inline insert (range : Range1ul) (set : RangeSet1ul) = set.Add range - | Geq _, Geq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [min] r } + /// Removes the given range from the set. + let inline remove (range : Range1ul) (set : RangeSet1ul) = set.Remove range - let ofSeq (s : seq) = - let mutable res = empty - for e in s do res <- insert e res - res + /// Returns the intersection of the set with the given range. + let inline intersect (range : Range1ul) (set : RangeSet1ul) = set.Intersect range - let inline ofList (l : list) = ofSeq l - let inline ofArray (l : Range1l[]) = ofSeq l + [] + let inline window (range : Range1ul) (set : RangeSet1ul) = intersect range set - let toSeq (r : RangeSet64) = r :> seq<_> - let toList (r : RangeSet64) = r |> Seq.toList - let toArray (r : RangeSet64) = r |> Seq.toArray + /// Returns whether the given value is contained in the range set. + let inline contains (value : uint64) (set : RangeSet1ul) = set.Contains value - let inline min (t : RangeSet64) = t.Min - let inline max (t : RangeSet64) = t.Max - let inline range (t : RangeSet64) = t.Range + /// Returns the number of disjoint ranges in the set. + let inline count (set : RangeSet1ul) = set.Count - let window (window : Range1l) (set : RangeSet64) = - let rangeMax = window.Max + 1L + /// Returns whether the set is empty. + let inline isEmpty (set : RangeSet1ul) = set.IsEmpty - let (l,rest) = set.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo window.Min > 0) minRange - let (inner,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax >= 0) minRange + /// Views the range set as a sequence. + let inline toSeq (set : RangeSet1ul) = set :> seq<_> - let inner = - match FingerTreeNode.lastOpt l with - | Some (Geq _) -> FingerTreeNode.prepend mm (geq window.Min) inner - | _ -> inner + /// Builds a list from the range set. + let inline toList (set : RangeSet1ul) = set.ToList() - let inner = - match FingerTreeNode.firstOpt r with - | Some (Leq _) -> FingerTreeNode.append mm (leq rangeMax) inner - | _ -> inner + /// Builds an array from the range set. + let inline toArray (set : RangeSet1ul) = set.ToArray() - { root = inner } diff --git a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_template.fs b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_template.fs index 87c8143c..bf7d3da6 100644 --- a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_template.fs +++ b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_template.fs @@ -3,235 +3,455 @@ open System open System.Collections open System.Collections.Generic -open FingerTreeImplementation - -//# foreach (var isLong in new[] { false, true }) { -//# var halfrange = isLong ? "HalfRange64" : "HalfRange"; -//# var rangeset = isLong ? "RangeSet64" : "RangeSet"; -//# var rangesetenumerator = isLong ? "RangeSet64Enumerator" : "RangeSetEnumerator"; -//# var range = isLong ? "Range1l" : "Range1i"; -//# var systype = isLong ? "Int64" : "Int32"; -//# var ft = isLong ? "int64" : "int32"; -//# var one = isLong ? "1L" : "1"; -[] -type private __halfrange__ = - struct - val mutable public IsMax : bool - val mutable public Value : __ft__ +open Aardvark.Base + +type internal HalfRangeKind = + | Left = 0 + | Right = 1 + +module private RangeSetUtils = + + let inline inc (value : 'T) = + let res = value + LanguagePrimitives.GenericOne<'T> + if res = Constant<'T>.ParseableMinValue then struct (Constant<'T>.ParseableMaxValue, true) + else struct (res, false) + + module MapExt = + let inline splitAt (key : 'K) (map : MapExt<'K, 'V>) = + let struct (l, _, _, _, r) = MapExt.splitV key map + struct (l, r) + + let inline tryMinValue (map : MapExt<'K, 'V>) = + MapExt.tryMinV map |> ValueOption.map (fun mk -> map.[mk]) + + let inline tryMaxValue (map : MapExt<'K, 'V>) = + MapExt.tryMaxV map |> ValueOption.map (fun mk -> map.[mk]) + + let inline maxValue (map : MapExt<'K, 'V>) = + map.[MapExt.max map] + +open RangeSetUtils + +//# var ltypes = new[] { "int32", "uint32", "int64", "uint64" }; +//# var sltypes = new[] { "Int32", "UInt32", "Int64", "UInt64" }; +//# var suffixes = new[] { "i", "ui", "l", "ul" }; +//# var literalSuffixes = new[] { "", "u", "L", "UL" }; +//# +//# for (int i = 0; i < ltypes.Length; i++) { +//# var ltype = ltypes[i]; +//# var suffix = suffixes[i]; +//# var range = "Range1" + suffix; +//# var rangeset = "RangeSet1" + suffix; +//# var enumerator = "RangeSetEnumerator1" + suffix; +//# var one = "1" + literalSuffixes[i]; +//# var minvalue = sltypes[i] + ".MinValue"; +//# var maxvalue = sltypes[i] + ".MaxValue"; +/// Set of ranges where overlapping and neighboring ranges are coalesced. +/// Note that ranges describe closed intervals. +[] +type __rangeset__ internal (store : MapExt<__ltype__, HalfRangeKind>) = + static let empty = __rangeset__(MapExt.empty) + + /// Empty range set. + static member Empty = empty + + /// Builds a range set of the given sequence of ranges. + static member OfSeq(ranges : __range__ seq) = + let arr = ranges |> Seq.toArray + if arr.Length = 0 then + empty + elif arr.Length = 1 then + let r = arr.[0] + __rangeset__(MapExt.ofListV [ + struct (r.Min, HalfRangeKind.Left) + if r.Max < __maxvalue__ then struct (r.Max + __one__, HalfRangeKind.Right) + ]) + else + // TODO: better impl possible (sort array and traverse) + arr |> Array.fold (fun s r -> s.Add r) empty + + member inline private x.Store = store + + // We cannot directly describe a range that ends at __maxvalue__ since the right half-range is inserted + // at max + 1. In that case the right-half range will be missing and the total count is odd. + member inline private x.HasMaxValue = store.Count % 2 = 1 + + /// Returns the minimum value in the range set or __maxvalue__ if the range is empty. + member x.Min = + match store.TryMinKeyV with + | ValueSome min -> min + | _ -> __maxvalue__ - new(m,v) = { IsMax = m; Value = v } + /// Returns the maximum value in the range set or __minvalue__ if the range is empty. + member x.Max = + if x.HasMaxValue then __maxvalue__ + else + match store.TryMaxKeyV with + | ValueSome max -> max - __one__ + | _ -> __minvalue__ + + /// Returns the total range spanned by the range set, i.e. [min, max]. + member inline x.Range = + __range__(x.Min, x.Max) + + /// Adds the given range to the set. + member x.Add(r : __range__) = + if r.Max < r.Min then + x + else + let min = r.Min + let struct (max, overflow) = inc r.Max + + let struct (lm, inner) = MapExt.splitAt min store + let rm = + if overflow then MapExt.empty + else sndv <| MapExt.splitAt max inner + + let before = MapExt.tryMaxValue lm + let after = MapExt.tryMinValue rm + + // If the set contained __maxvalue__ or we have overflown, we must not add an explicit right half-range. + // __maxvalue__ is stored implicitly. + let fixRightBoundary = + if x.HasMaxValue || overflow then + id + else + MapExt.add max HalfRangeKind.Right + + let newStore = + match before, after with + | ValueNone, ValueNone -> + MapExt.ofListV [ + struct (min, HalfRangeKind.Left) + ] + |> fixRightBoundary + + | ValueSome HalfRangeKind.Right, ValueNone -> + lm + |> MapExt.add min HalfRangeKind.Left + |> fixRightBoundary + + | ValueSome HalfRangeKind.Left, ValueNone -> + lm + |> fixRightBoundary + + | ValueNone, ValueSome HalfRangeKind.Left -> + rm + |> MapExt.add min HalfRangeKind.Left + |> MapExt.add max HalfRangeKind.Right + + | ValueNone, ValueSome HalfRangeKind.Right -> + rm + |> MapExt.add min HalfRangeKind.Left + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Left); struct (max, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (max, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm + + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Right -> + MapExt.union lm rm + + | _ -> + failwithf "impossible" + + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + __rangeset__(newStore) + + /// Removes the given range from the set. + member x.Remove(r : __range__) = + if r.Max < r.Min then + x + else + let min = r.Min + let struct (max, overflow) = inc r.Max + + let struct (lm, inner) = MapExt.splitAt min store + let rm = + if overflow then MapExt.empty + else sndv <| MapExt.splitAt max inner + + let before = MapExt.tryMaxValue lm + let after = MapExt.tryMinValue rm + + // If the set contained __maxvalue__ and we have not overflown, there is still a range [max, __maxvalue__] + let fixRightBoundary = + if x.HasMaxValue && not overflow then + MapExt.add max HalfRangeKind.Left + else + id + + let newStore = + match before, after with + | ValueNone, ValueNone -> + MapExt.empty + |> fixRightBoundary + + | ValueSome HalfRangeKind.Right, ValueNone -> + lm + |> fixRightBoundary + + | ValueSome HalfRangeKind.Left, ValueNone -> + lm + |> MapExt.add min HalfRangeKind.Right + |> fixRightBoundary + + | ValueNone, ValueSome HalfRangeKind.Left -> + rm - override x.GetHashCode() = - if x.IsMax then 0 - else x.Value.GetHashCode() + | ValueNone, ValueSome HalfRangeKind.Right -> + rm + |> MapExt.add max HalfRangeKind.Left - override x.Equals o = - match o with - | :? __halfrange__ as o -> - x.IsMax = o.IsMax && x.Value = o.Value - | _ -> - false + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Left -> + MapExt.union lm rm - member x.CompareTo (o : __halfrange__) = - let c = x.Value.CompareTo o.Value - if c = 0 then - if x.IsMax = o.IsMax then 0 - else (if x.IsMax then 1 else -1) - else - c + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Left -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Right) ] + MapExt.union (MapExt.union lm self) rm - interface IComparable with - member x.CompareTo o = - match o with - | :? __halfrange__ as o -> x.CompareTo(o) - | _ -> failwith "uncomparable" - end + | ValueSome HalfRangeKind.Right, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (max, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm + | ValueSome HalfRangeKind.Left, ValueSome HalfRangeKind.Right -> + let self = MapExt.ofListV [ struct (min, HalfRangeKind.Right); struct (max, HalfRangeKind.Left) ] + MapExt.union (MapExt.union lm self) rm -[] -type __rangeset__ = private { root : FingerTreeNode<__halfrange__, __halfrange__> } with - - member private x.AsString = - x |> Seq.map (sprintf "%A") - |> String.concat "; " - |> sprintf "set [%s]" + | _ -> + failwithf "impossible" - member x.Min = - match x.root |> FingerTreeNode.firstOpt with - | Some f -> f.Value - | _ -> __systype__.MaxValue + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + __rangeset__(newStore) - member x.Max = - match x.root |> FingerTreeNode.lastOpt with - | Some f -> f.Value - | _ -> __systype__.MinValue + /// Returns the intersection of the set with the given range. + member x.Intersect(r : __range__) = + if r.Max < r.Min then + empty + else + let min = r.Min + let struct (max, overflow) = inc r.Max - member x.Range = - match FingerTreeNode.firstOpt x.root, FingerTreeNode.lastOpt x.root with - | Some min, Some max -> __range__(min.Value, max.Value) - | _ -> __range__.Invalid + let inner = + store + |> MapExt.splitAt min |> sndv + |> if not overflow then MapExt.splitAt max >> fstv else id - interface IEnumerable with - member x.GetEnumerator() = new __rangesetenumerator__(FingerTreeImplementation.FingerTreeNode.getEnumeratorFw x.root) :> IEnumerator + let newStore = + inner + |> if x.Contains r.Min then MapExt.add min HalfRangeKind.Left else id + |> if x.Contains r.Max && not overflow then MapExt.add max HalfRangeKind.Right else id - interface IEnumerable<__range__> with - member x.GetEnumerator() = new __rangesetenumerator__(FingerTreeImplementation.FingerTreeNode.getEnumeratorFw x.root) :> _ + assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) + __rangeset__(newStore) + /// Returns whether the given value is contained in the range set. + member x.Contains(v : __ltype__) = + let struct (l, s, _) = MapExt.neighboursV v store + match s with + | ValueSome (_, k) -> k = HalfRangeKind.Left + | _ -> + match l with + | ValueSome (_, HalfRangeKind.Left) -> true + | _ -> false -and private __rangesetenumerator__(i : IEnumerator<__halfrange__>) = - - let mutable last = __halfrange__() - let mutable current = __halfrange__() + /// Returns the number of disjoint ranges in the set. + member x.Count = + (store.Count + 1) / 2 - member x.Current = __range__(last.Value, current.Value - __one__) + /// Returns whether the set is empty. + member inline x.IsEmpty = + x.Count = 0 - interface IEnumerator with - member x.MoveNext() = - if i.MoveNext() then - last <- i.Current - if i.MoveNext() then - current <- i.Current - true - else - false - else - false + /// Builds an array from the range set. + member x.ToArray() = + let arr = Array.zeroCreate x.Count - member x.Current = x.Current :> obj + let rec write (i : int) (l : struct (__ltype__ * HalfRangeKind) list) = + match l with + | struct (l, _) :: struct (r, _) :: rest -> + arr.[i] <- __range__(l, r - __one__) + write (i + 1) rest - member x.Reset() = - i.Reset() + | [struct (l, HalfRangeKind.Left)] when i = x.Count - 1 -> + arr.[i] <- __range__(l, __maxvalue__) - interface IEnumerator<__range__> with - member x.Current = x.Current - member x.Dispose() = i.Dispose() + | [_] -> failwith "bad RangeSet" -[] -module __rangeset__ = - let private mm = - { - quantify = fun (r : __halfrange__) -> r - mempty = __halfrange__(false, __systype__.MinValue) - mappend = fun l r -> if l.CompareTo r > 0 then l else r - } + | [] -> () + + store |> MapExt.toListV |> write 0 + arr + + /// Builds a list from the range set. + member x.ToList() = + let rec build (accum : __range__ list) (l : struct (__ltype__ * HalfRangeKind) list) = + match l with + | struct (l, _) :: struct (r, _) :: rest -> + build (__range__(l, r - __one__) :: accum) rest - let private minRange = __halfrange__(false, __systype__.MinValue) + | [struct (l, HalfRangeKind.Left)] -> + build (__range__(l, __maxvalue__) :: accum) [] - let inline private leq v = __halfrange__(true, v) - let inline private geq v = __halfrange__(false, v) + | [_] -> failwith "bad RangeSet" - let inline private (|Leq|Geq|) (r : __halfrange__) = - if r.IsMax then Leq r.Value - else Geq r.Value + | [] -> List.rev accum - let empty = { root = Empty } + store |> MapExt.toListV |> build [] - let insert (range : __range__) (t : __rangeset__) = - let rangeMax = range.Max + __one__ + /// Views the range set as a sequence. + member x.ToSeq() = + x :> seq<_> - let (l,rest) = t.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo range.Min >= 0) minRange - let (_,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax > 0) minRange + member inline private x.Equals(other : __rangeset__) = + store = other.Store - let max = leq rangeMax - let min = geq range.Min + override x.Equals(other : obj) = + match other with + | :? __rangeset__ as o -> x.Equals o + | _ -> false - match FingerTreeNode.lastOpt l, FingerTreeNode.firstOpt r with - | None, None -> - { root = Deep(max, One(min), Empty, One(max)) } + override x.GetHashCode() = + store.GetHashCode() - | Some lmax, None -> - match lmax with - | Leq _ -> { root = l |> FingerTreeNode.append mm min |> FingerTreeNode.append mm max } - | Geq _ -> { root = l |> FingerTreeNode.append mm max } + member private x.AsString = x.ToString() - | None, Some rmin -> - match rmin with - | Leq _ -> { root = r |> FingerTreeNode.prepend mm min } - | Geq _ -> { root = r |> FingerTreeNode.prepend mm max |> FingerTreeNode.prepend mm min } + override x.ToString() = + let content = + x |> Seq.map (fun r -> + $"[{r.Min}, {r.Max}]" + ) + |> String.concat "; " - | Some lmax, Some rmin -> - match lmax, rmin with - | Leq _, Geq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [min;max] r } + $"ranges [{content}]" - | Geq _, Leq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [] r } + member x.GetEnumerator() = + new __enumerator__((store :> seq<_>).GetEnumerator()) - | Leq _, Leq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [min] r } + interface IEquatable<__rangeset__> with + member x.Equals(other) = x.Equals(other) - | Geq _, Geq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [max] r } + interface IEnumerable with + member x.GetEnumerator() = new __enumerator__((store :> seq<_>).GetEnumerator()) :> _ + + interface IEnumerable<__range__> with + member x.GetEnumerator() = new __enumerator__((store :> seq<_>).GetEnumerator()) :> _ + +// TODO: MapExt should use a struct enumerator and return it directly. +// That way we could get rid of allocations. +and __enumerator__ = + struct + val private Inner : IEnumerator> + val mutable private Left : KeyValuePair<__ltype__, HalfRangeKind> + val mutable private Right : KeyValuePair<__ltype__, HalfRangeKind> + + internal new (inner : IEnumerator>) = + { Inner = inner + Left = Unchecked.defaultof<_> + Right = Unchecked.defaultof<_> } + + member x.MoveNext() = + if x.Inner.MoveNext() then + x.Left <- x.Inner.Current + if x.Inner.MoveNext() then + x.Right <- x.Inner.Current + true + else + if x.Left.Value = HalfRangeKind.Left then + x.Right <- KeyValuePair(__minvalue__, HalfRangeKind.Right) // MaxValue + 1 + true + else + failwithf "bad RangeSet" + else + false + + member x.Reset() = + x.Inner.Reset() + x.Left <- Unchecked.defaultof<_> + x.Right <- Unchecked.defaultof<_> + + member x.Current = + assert (x.Left.Value = HalfRangeKind.Left && x.Right.Value = HalfRangeKind.Right) + __range__(x.Left.Key, x.Right.Key - __one__) + + member x.Dispose() = + x.Inner.Dispose() + x.Left <- Unchecked.defaultof<_> + x.Right <- Unchecked.defaultof<_> + + interface IEnumerator with + member x.MoveNext() = x.MoveNext() + member x.Current = x.Current :> obj + member x.Reset() = x.Reset() + + interface IEnumerator<__range__> with + member x.Dispose() = x.Dispose() + member x.Current = x.Current + end + +[] +module __rangeset__ = - let remove (range : __range__) (t : __rangeset__) = - let rangeMax = range.Max + __one__ + /// Empty range set. + let empty = __rangeset__.Empty - let (l,rest) = t.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo range.Min >= 0) minRange - let (_,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax > 0) minRange + /// Returns the minimum value in the range set or __maxvalue__ if the range is empty. + let inline min (set : __rangeset__) = set.Min - let max = geq rangeMax - let min = leq range.Min + /// Returns the maximum value in the range set or __minvalue__ if the range is empty. + let inline max (set : __rangeset__) = set.Max - match FingerTreeNode.lastOpt l, FingerTreeNode.firstOpt r with - | None, None -> - { root = Empty } + /// Returns the total range spanned by the range set, i.e. [min, max]. + let inline range (set : __rangeset__) = set.Range - | Some lmax, None -> - match lmax with - | Leq _ -> { root = l } - | Geq _ -> { root = l |> FingerTreeNode.append mm min } + /// Builds a range set of the given sequence of ranges. + let inline ofSeq (ranges : seq<__range__>) = __rangeset__.OfSeq ranges - | None, Some rmin -> - match rmin with - | Leq _ -> { root = r |> FingerTreeNode.prepend mm max } - | Geq _ -> { root = r } + /// Builds a range set of the given list of ranges. + let inline ofList (ranges : __range__ list) = __rangeset__.OfSeq ranges - | Some lmax, Some rmin -> - match lmax, rmin with - | Leq _, Geq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [] r } + /// Builds a range set of the given array of ranges. + let inline ofArray (ranges : __range__[]) = __rangeset__.OfSeq ranges - | Geq _, Leq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [min; max] r } + /// Adds the given range to the set. + let inline add (range : __range__) (set : __rangeset__) = set.Add range - | Leq _, Leq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [max] r } + [] + let inline insert (range : __range__) (set : __rangeset__) = set.Add range - | Geq _, Geq _ -> - { root = FingerTreeNode.concatWithMiddle mm l [min] r } + /// Removes the given range from the set. + let inline remove (range : __range__) (set : __rangeset__) = set.Remove range - let ofSeq (s : seq<__range__>) = - let mutable res = empty - for e in s do res <- insert e res - res + /// Returns the intersection of the set with the given range. + let inline intersect (range : __range__) (set : __rangeset__) = set.Intersect range - let inline ofList (l : list<__range__>) = ofSeq l - let inline ofArray (l : __range__[]) = ofSeq l + [] + let inline window (range : __range__) (set : __rangeset__) = intersect range set - let toSeq (r : __rangeset__) = r :> seq<_> - let toList (r : __rangeset__) = r |> Seq.toList - let toArray (r : __rangeset__) = r |> Seq.toArray + /// Returns whether the given value is contained in the range set. + let inline contains (value : __ltype__) (set : __rangeset__) = set.Contains value - let inline min (t : __rangeset__) = t.Min - let inline max (t : __rangeset__) = t.Max - let inline range (t : __rangeset__) = t.Range + /// Returns the number of disjoint ranges in the set. + let inline count (set : __rangeset__) = set.Count - let window (window : __range__) (set : __rangeset__) = - let rangeMax = window.Max + __one__ + /// Returns whether the set is empty. + let inline isEmpty (set : __rangeset__) = set.IsEmpty - let (l,rest) = set.root |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo window.Min > 0) minRange - let (inner,r) = rest |> FingerTreeNode.splitFirstRight mm (fun v -> v.Value.CompareTo rangeMax >= 0) minRange + /// Views the range set as a sequence. + let inline toSeq (set : __rangeset__) = set :> seq<_> - let inner = - match FingerTreeNode.lastOpt l with - | Some (Geq _) -> FingerTreeNode.prepend mm (geq window.Min) inner - | _ -> inner + /// Builds a list from the range set. + let inline toList (set : __rangeset__) = set.ToList() - let inner = - match FingerTreeNode.firstOpt r with - | Some (Leq _) -> FingerTreeNode.append mm (leq rangeMax) inner - | _ -> inner + /// Builds an array from the range set. + let inline toArray (set : __rangeset__) = set.ToArray() - { root = inner } //# } \ No newline at end of file diff --git a/src/Tests/Aardvark.Base.FSharp.Benchmarks/Aardvark.Base.FSharp.Benchmarks.fsproj b/src/Tests/Aardvark.Base.FSharp.Benchmarks/Aardvark.Base.FSharp.Benchmarks.fsproj index 291f4317..4f7b23db 100644 --- a/src/Tests/Aardvark.Base.FSharp.Benchmarks/Aardvark.Base.FSharp.Benchmarks.fsproj +++ b/src/Tests/Aardvark.Base.FSharp.Benchmarks/Aardvark.Base.FSharp.Benchmarks.fsproj @@ -1,9 +1,10 @@ - + + Exe net6.0 - true false + true 3389;3390;3395 false @@ -20,6 +21,8 @@ true + + diff --git a/src/Tests/Aardvark.Base.FSharp.Benchmarks/RangeSet.fs b/src/Tests/Aardvark.Base.FSharp.Benchmarks/RangeSet.fs new file mode 100644 index 00000000..54c315f8 --- /dev/null +++ b/src/Tests/Aardvark.Base.FSharp.Benchmarks/RangeSet.fs @@ -0,0 +1,275 @@ +namespace Aardvark.Base.FSharp.Benchmarks + +#nowarn "44" + +open System +open Aardvark.Base + +module RangeSetTests = + open FsUnit + open NUnit.Framework + + [] + let ``[RangeSet] Insert`` (maxValue : bool) (mergeWithMaxValue : bool) = + let mutable s1 = RangeSet64.empty + let mutable s2 = RangeSet1l.empty + + let add (r : Range1l) = + s1 <- s1 |> RangeSet64.insert r + s2 <- s2 |> RangeSet1l.add r + + let equal (expected : Range1l list) = + // Old implementation broken for MaxValue! + if not maxValue then + Seq.toList s1 |> should equal (Seq.toList s2) + + Seq.toList s2 |> should equal expected + + add <| Range1l(0L, 1L) + add <| Range1l(1L, 2L) + equal [ Range1l(0L, 2L) ] + + add <| Range1l(3L, 4L) + equal [ Range1l(0L, 4L) ] + + add <| Range1l(6L, 8L) + equal [ Range1l(0L, 4L); Range1l(6L, 8L) ] + + if maxValue then + add <| Range1l Int64.MaxValue + equal [ Range1l(0L, 4L); Range1l(6L, 8L); Range1l(Int64.MaxValue) ] + + add <| Range1l(9L, 14L) + equal [ Range1l(0L, 4L); Range1l(6L, 14L); Range1l(Int64.MaxValue) ] + + add <| Range1l(42L, if mergeWithMaxValue then Int64.MaxValue - 1L else Int64.MaxValue) + equal [ Range1l(0L, 4L); Range1l(6L, 14L); Range1l(42L, Int64.MaxValue) ] + + [] + let ``[RangeSet] Remove`` (maxValue : bool) = + let init = [ Range1l(0L, 4L); Range1l(6L, 8L); if maxValue then Range1l(42L, Int64.MaxValue) ] + let mutable s1 = RangeSet64.ofList init + let mutable s2 = RangeSet1l.ofList init + + let rem (r : Range1l) = + s1 <- s1 |> RangeSet64.remove r + s2 <- s2 |> RangeSet1l.remove r + + let equal (expected : Range1l list) = + // Old implementation broken for MaxValue! + if not maxValue then + Seq.toList s1 |> should equal (Seq.toList s2) + + Seq.toList s2 |> should equal expected + + rem <| Range1l(3L, 3L) + equal [ Range1l(0L, 2L); Range1l(4L, 4L); Range1l(6L, 8L); if maxValue then Range1l(42L, Int64.MaxValue) ] + + rem <| Range1l(4L, 4L) + equal [ Range1l(0L, 2L); Range1l(6L, 8L); if maxValue then Range1l(42L, Int64.MaxValue) ] + + rem <| Range1l(2L, 7L) + equal [ Range1l(0L, 1L); Range1l(8L, 8L); if maxValue then Range1l(42L, Int64.MaxValue) ] + + rem <| Range1l(-12L, 7L) + equal [ Range1l(8L, 8L); if maxValue then Range1l(42L, Int64.MaxValue) ] + + if maxValue then + rem <| Range1l(0L, Int64.MaxValue - 1L) + equal [ Range1l(Int64.MaxValue) ] + + rem <| Range1l(Int64.MaxValue) + equal [] + + s2 <- RangeSet1l.ofList [ Range1l(0L, Int64.MaxValue) ] + rem <| Range1l(Int64.MaxValue) + equal [ Range1l(0L, Int64.MaxValue - 1L) ] + + [] + let ``[RangeSet] ToList`` (maxValue : bool) = + let init = [ Range1l(0L, 4L); Range1l(6L, 8L); if maxValue then Range1l(10L, Int64.MaxValue) ] + let mutable s1 = RangeSet64.ofList init + let mutable s2 = RangeSet1l.ofList init + + RangeSet64.toList s1 |> should equal init + RangeSet1l.toList s2 |> should equal init + + [] + let ``[RangeSet] ToArray`` (maxValue : bool) = + let init = [ Range1l(0L, 4L); Range1l(6L, 8L); if maxValue then Range1l(10L, Int64.MaxValue) ] + let mutable s1 = RangeSet64.ofList init + let mutable s2 = RangeSet1l.ofList init + + RangeSet64.toArray s1 |> should equal (Array.ofList init) + RangeSet1l.toArray s2 |> should equal (Array.ofList init) + + [] + let ``[RangeSet] ToSeq`` (maxValue : bool) = + let init = [ Range1l(0L, 4L); Range1l(6L, 8L); if maxValue then Range1l(10L, Int64.MaxValue) ] + let mutable s1 = RangeSet64.ofList init + let mutable s2 = RangeSet1l.ofList init + + RangeSet64.toSeq s1 |> Seq.toList |> should equal init + RangeSet1l.toSeq s2 |> Seq.toList |> should equal init + + [] + let ``[RangeSet] Min / Max / Range`` (maxValue : bool) = + let init = [ Range1l(-3L, -2L); Range1l(1L, 4L); Range1l(6L, 8L); if maxValue then Range1l(10L, Int64.MaxValue) ] + let min = init |> List.map (fun r -> r.Min) |> List.min + let max = init |> List.map (fun r -> r.Max) |> List.max + + let mutable s1 = RangeSet64.ofList init + let mutable s2 = RangeSet1l.ofList init + + s1.Min |> should equal s2.Min + s2.Min |> should equal min + + s1.Max |> should equal (max + 1L) // Old implementation is inconsistent! + s2.Max |> should equal max + + s1.Range |> should equal (Range1l(min, max + 1L)) + s2.Range |> should equal (Range1l(min, max)) + + [] + let ``[RangeSet] Intersect`` (hasMaxValue : bool) (testMaxValue : bool) = + let init = [ Range1l(-3L, -2L); Range1l(1L, 4L); Range1l(6L, 8L); if hasMaxValue then Range1l(10L, Int64.MaxValue)] + let s1 = RangeSet64.ofList init + let s2 = RangeSet1l.ofList init + + let intersect (r : Range1l) = + let r1 = s1 |> RangeSet64.window r + let r2 = s2 |> RangeSet1l.intersect r + + // Old implementation broken for MaxValue! + if not (hasMaxValue || testMaxValue) then + (RangeSet64.toList r1) |> should equal (RangeSet1l.toList r2) + + RangeSet1l.toList r2 + + if testMaxValue then + Range1l(2L, Int64.MaxValue) |> intersect |> should equal [ Range1l(2L, 4L); Range1l(6L, 8L); if hasMaxValue then Range1l(10L, Int64.MaxValue) ] + Range1l(5L, Int64.MaxValue) |> intersect |> should equal [ Range1l(6L, 8L); if hasMaxValue then Range1l(10L, Int64.MaxValue) ] + Range1l(1L, Int64.MaxValue) |> intersect |> should equal [ Range1l(1L, 4L); Range1l(6L, 8L); if hasMaxValue then Range1l(10L, Int64.MaxValue) ] + Range1l(4L, Int64.MaxValue) |> intersect |> should equal [ Range1l(4L, 4L); Range1l(6L, 8L); if hasMaxValue then Range1l(10L, Int64.MaxValue) ] + Range1l(-3L, Int64.MaxValue) |> intersect |> should equal init + Range1l(-42L, Int64.MaxValue) |> intersect |> should equal init + else + Range1l(2L, 3L) |> intersect |> should equal [ Range1l(2L, 3L) ] + Range1l(5L, 6L) |> intersect |> should equal [ Range1l(6L, 6L) ] + Range1l(-3L, 8L) |> intersect |> should equal [ Range1l(-3L, -2L); Range1l(1L, 4L); Range1l(6L, 8L) ] + Range1l(-42L, 42L) |> intersect |> should equal [ Range1l(-3L, -2L); Range1l(1L, 4L); Range1l(6L, 8L); if hasMaxValue then Range1l(10L, 42L) ] + Range1l(-3L, -2L) |> intersect |> should equal [ Range1l(-3L, -2L) ] + Range1l(-2L, -1L) |> intersect |> should equal [ Range1l(-2L, -2L); ] + Range1l(-2L, 1L) |> intersect |> should equal [ Range1l(-2L, -2L); Range1l(1L, 1L) ] + Range1l(-3L, 3L) |> intersect |> should equal [ Range1l(-3L, -2L); Range1l(1L, 3L) ] + Range1l(-2L, 8L) |> intersect |> should equal [ Range1l(-2L, -2L); Range1l(1L, 4L); Range1l(6L, 8L) ] + + [] + let ``[RangeSet] Enumerator`` (maxValue : bool) = + let init = [ Range1l(-3L, -2L); Range1l(1L, 4L); if maxValue then Range1l(6L, Int64.MaxValue) ] + let s1 = RangeSet64.ofList init + let s2 = RangeSet1l.ofList init + + let expected = Seq.ofList init + s1 :> seq<_> |> should equal (s2 :> seq<_>) + s2 :> seq<_> |> should equal expected + + [] + let ``[RangeSet] Equality`` (maxValue : bool) = + let init = [ Range1l(-3L, -2L); Range1l(1L, 4L); Range1l(6L, 8L); if maxValue then Range1l(34L, Int64.MaxValue) ] + let mutable a1 = RangeSet64.ofList init + let mutable b1 = RangeSet64.ofList init + let mutable a2 = RangeSet1l.ofList init + let mutable b2 = RangeSet1l.ofList init + + (a1 = b1) |> should be True + (a2 = b2) |> should be True + + +module RangeSetBenchmarks = + open BenchmarkDotNet.Attributes; + + [] + type ``RangeSets``() = + let rnd = RandomSystem(0) + + let mutable setOld = RangeSet64.empty + let mutable setNew = RangeSet1l.empty + let mutable ranges = List.empty + let mutable ranges2 = List.empty + + [] + val mutable Count : int + + member private x.RandomRange() = + let mutable l = Int64.MaxValue + let mutable r = Int64.MinValue + + while l > r do + l <- rnd.UniformLong() + r <- l + rnd.UniformLong(100000L) + + Range1l(l, r) + + member private x.Generate(count : int) = + List.init count (ignore >> x.RandomRange) + + [] + member x.Init() = + ranges <- x.Generate x.Count + ranges2 <- x.Generate x.Count + setOld <- RangeSet64.ofList ranges + setNew <- RangeSet1l.ofList ranges + + + [] + member x.InsertOld() = + let mutable set = RangeSet64.empty + + for r in ranges do + set <- set |> RangeSet64.insert r + + set + + [] + member x.InsertNew() = + let mutable set = RangeSet1l.empty + + for r in ranges do + set <- set |> RangeSet1l.add r + + set + + + [] + member x.RemoveOld() = + for r in ranges2 do + setOld <- setOld |> RangeSet64.remove r + + setOld + + [] + member x.RemoveNew() = + for r in ranges2 do + setNew <- setNew |> RangeSet1l.remove r + + setNew + + + [] + member x.IterateOld() = + let mutable cnt = Int64.MinValue + + for r in setOld do + cnt <- cnt + r.Min + + cnt + + [] + member x.IterateNew() = + let mutable cnt = Int64.MinValue + + for r in setNew do + cnt <- cnt + r.Min + + cnt \ No newline at end of file diff --git a/src/Tests/Aardvark.Base.FSharp.Benchmarks/paket.references b/src/Tests/Aardvark.Base.FSharp.Benchmarks/paket.references index 55672402..d54763f3 100644 --- a/src/Tests/Aardvark.Base.FSharp.Benchmarks/paket.references +++ b/src/Tests/Aardvark.Base.FSharp.Benchmarks/paket.references @@ -3,5 +3,6 @@ group Test FSharp.Core BenchmarkDotNet NUnit +FsUnit NUnit3TestAdapter Microsoft.NET.Test.Sdk \ No newline at end of file From cfd69ce2370f9ad532bd4ee4058d148fa59ad11c Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 12 Oct 2023 19:56:14 +0200 Subject: [PATCH 06/45] [RangeSet] Improve ofSeq, ofList, ofArray --- .../Datastructures/Immutable/RangeSet_auto.fs | 232 ++++++++++++------ .../Immutable/RangeSet_template.fs | 58 +++-- .../RangeSet.fs | 29 +++ 3 files changed, 224 insertions(+), 95 deletions(-) diff --git a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_auto.fs b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_auto.fs index cc399c3d..0d88761b 100644 --- a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_auto.fs +++ b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_auto.fs @@ -41,21 +41,6 @@ type RangeSet1i internal (store : MapExt) = /// Empty range set. static member Empty = empty - /// Builds a range set of the given sequence of ranges. - static member OfSeq(ranges : Range1i seq) = - let arr = ranges |> Seq.toArray - if arr.Length = 0 then - empty - elif arr.Length = 1 then - let r = arr.[0] - RangeSet1i(MapExt.ofListV [ - struct (r.Min, HalfRangeKind.Left) - if r.Max < Int32.MaxValue then struct (r.Max + 1, HalfRangeKind.Right) - ]) - else - // TODO: better impl possible (sort array and traverse) - arr |> Array.fold (fun s r -> s.Add r) empty - member inline private x.Store = store // We cannot directly describe a range that ends at Int32.MaxValue since the right half-range is inserted @@ -397,14 +382,49 @@ module RangeSet1i = /// Returns the total range spanned by the range set, i.e. [min, max]. let inline range (set : RangeSet1i) = set.Range - /// Builds a range set of the given sequence of ranges. - let inline ofSeq (ranges : seq) = RangeSet1i.OfSeq ranges + let inline private getHalfRanges (r : Range1i) = + [ struct (r.Min, HalfRangeKind.Left) + if r.Max < Int32.MaxValue then struct (r.Max + 1, HalfRangeKind.Right) ] + + let inline private ofRange (r : Range1i) = + RangeSet1i(MapExt.ofListV <| getHalfRanges r) + + let private ofRanges (ranges : seq) = + let halves = + ranges + |> Seq.toList + |> List.collect getHalfRanges + |> List.sortBy fstv + + let mutable level = 0 + let result = ResizeArray() + + for (struct (i, k) as h) in halves do + if k = HalfRangeKind.Left then + if level = 0 then result.Add h + level <- level + 1 + else + level <- level - 1 + if level = 0 then result.Add h + + RangeSet1i(MapExt.ofSeqV result) /// Builds a range set of the given list of ranges. - let inline ofList (ranges : Range1i list) = RangeSet1i.OfSeq ranges + let ofList (ranges : Range1i list) = + match ranges with + | [] -> empty + | [r] -> ofRange r + | _ -> ofRanges ranges /// Builds a range set of the given array of ranges. - let inline ofArray (ranges : Range1i[]) = RangeSet1i.OfSeq ranges + let ofArray (ranges : Range1i[]) = + if ranges.Length = 0 then empty + elif ranges.Length = 1 then ofRange ranges.[0] + else ofRanges ranges + + /// Builds a range set of the given sequence of ranges. + let inline ofSeq (ranges : seq) = + ofList <| Seq.toList ranges /// Adds the given range to the set. let inline add (range : Range1i) (set : RangeSet1i) = set.Add range @@ -449,21 +469,6 @@ type RangeSet1ui internal (store : MapExt) = /// Empty range set. static member Empty = empty - /// Builds a range set of the given sequence of ranges. - static member OfSeq(ranges : Range1ui seq) = - let arr = ranges |> Seq.toArray - if arr.Length = 0 then - empty - elif arr.Length = 1 then - let r = arr.[0] - RangeSet1ui(MapExt.ofListV [ - struct (r.Min, HalfRangeKind.Left) - if r.Max < UInt32.MaxValue then struct (r.Max + 1u, HalfRangeKind.Right) - ]) - else - // TODO: better impl possible (sort array and traverse) - arr |> Array.fold (fun s r -> s.Add r) empty - member inline private x.Store = store // We cannot directly describe a range that ends at UInt32.MaxValue since the right half-range is inserted @@ -805,14 +810,49 @@ module RangeSet1ui = /// Returns the total range spanned by the range set, i.e. [min, max]. let inline range (set : RangeSet1ui) = set.Range - /// Builds a range set of the given sequence of ranges. - let inline ofSeq (ranges : seq) = RangeSet1ui.OfSeq ranges + let inline private getHalfRanges (r : Range1ui) = + [ struct (r.Min, HalfRangeKind.Left) + if r.Max < UInt32.MaxValue then struct (r.Max + 1u, HalfRangeKind.Right) ] + + let inline private ofRange (r : Range1ui) = + RangeSet1ui(MapExt.ofListV <| getHalfRanges r) + + let private ofRanges (ranges : seq) = + let halves = + ranges + |> Seq.toList + |> List.collect getHalfRanges + |> List.sortBy fstv + + let mutable level = 0 + let result = ResizeArray() + + for (struct (i, k) as h) in halves do + if k = HalfRangeKind.Left then + if level = 0 then result.Add h + level <- level + 1 + else + level <- level - 1 + if level = 0 then result.Add h + + RangeSet1ui(MapExt.ofSeqV result) /// Builds a range set of the given list of ranges. - let inline ofList (ranges : Range1ui list) = RangeSet1ui.OfSeq ranges + let ofList (ranges : Range1ui list) = + match ranges with + | [] -> empty + | [r] -> ofRange r + | _ -> ofRanges ranges /// Builds a range set of the given array of ranges. - let inline ofArray (ranges : Range1ui[]) = RangeSet1ui.OfSeq ranges + let ofArray (ranges : Range1ui[]) = + if ranges.Length = 0 then empty + elif ranges.Length = 1 then ofRange ranges.[0] + else ofRanges ranges + + /// Builds a range set of the given sequence of ranges. + let inline ofSeq (ranges : seq) = + ofList <| Seq.toList ranges /// Adds the given range to the set. let inline add (range : Range1ui) (set : RangeSet1ui) = set.Add range @@ -857,21 +897,6 @@ type RangeSet1l internal (store : MapExt) = /// Empty range set. static member Empty = empty - /// Builds a range set of the given sequence of ranges. - static member OfSeq(ranges : Range1l seq) = - let arr = ranges |> Seq.toArray - if arr.Length = 0 then - empty - elif arr.Length = 1 then - let r = arr.[0] - RangeSet1l(MapExt.ofListV [ - struct (r.Min, HalfRangeKind.Left) - if r.Max < Int64.MaxValue then struct (r.Max + 1L, HalfRangeKind.Right) - ]) - else - // TODO: better impl possible (sort array and traverse) - arr |> Array.fold (fun s r -> s.Add r) empty - member inline private x.Store = store // We cannot directly describe a range that ends at Int64.MaxValue since the right half-range is inserted @@ -1213,14 +1238,49 @@ module RangeSet1l = /// Returns the total range spanned by the range set, i.e. [min, max]. let inline range (set : RangeSet1l) = set.Range - /// Builds a range set of the given sequence of ranges. - let inline ofSeq (ranges : seq) = RangeSet1l.OfSeq ranges + let inline private getHalfRanges (r : Range1l) = + [ struct (r.Min, HalfRangeKind.Left) + if r.Max < Int64.MaxValue then struct (r.Max + 1L, HalfRangeKind.Right) ] + + let inline private ofRange (r : Range1l) = + RangeSet1l(MapExt.ofListV <| getHalfRanges r) + + let private ofRanges (ranges : seq) = + let halves = + ranges + |> Seq.toList + |> List.collect getHalfRanges + |> List.sortBy fstv + + let mutable level = 0 + let result = ResizeArray() + + for (struct (i, k) as h) in halves do + if k = HalfRangeKind.Left then + if level = 0 then result.Add h + level <- level + 1 + else + level <- level - 1 + if level = 0 then result.Add h + + RangeSet1l(MapExt.ofSeqV result) /// Builds a range set of the given list of ranges. - let inline ofList (ranges : Range1l list) = RangeSet1l.OfSeq ranges + let ofList (ranges : Range1l list) = + match ranges with + | [] -> empty + | [r] -> ofRange r + | _ -> ofRanges ranges /// Builds a range set of the given array of ranges. - let inline ofArray (ranges : Range1l[]) = RangeSet1l.OfSeq ranges + let ofArray (ranges : Range1l[]) = + if ranges.Length = 0 then empty + elif ranges.Length = 1 then ofRange ranges.[0] + else ofRanges ranges + + /// Builds a range set of the given sequence of ranges. + let inline ofSeq (ranges : seq) = + ofList <| Seq.toList ranges /// Adds the given range to the set. let inline add (range : Range1l) (set : RangeSet1l) = set.Add range @@ -1265,21 +1325,6 @@ type RangeSet1ul internal (store : MapExt) = /// Empty range set. static member Empty = empty - /// Builds a range set of the given sequence of ranges. - static member OfSeq(ranges : Range1ul seq) = - let arr = ranges |> Seq.toArray - if arr.Length = 0 then - empty - elif arr.Length = 1 then - let r = arr.[0] - RangeSet1ul(MapExt.ofListV [ - struct (r.Min, HalfRangeKind.Left) - if r.Max < UInt64.MaxValue then struct (r.Max + 1UL, HalfRangeKind.Right) - ]) - else - // TODO: better impl possible (sort array and traverse) - arr |> Array.fold (fun s r -> s.Add r) empty - member inline private x.Store = store // We cannot directly describe a range that ends at UInt64.MaxValue since the right half-range is inserted @@ -1621,14 +1666,49 @@ module RangeSet1ul = /// Returns the total range spanned by the range set, i.e. [min, max]. let inline range (set : RangeSet1ul) = set.Range - /// Builds a range set of the given sequence of ranges. - let inline ofSeq (ranges : seq) = RangeSet1ul.OfSeq ranges + let inline private getHalfRanges (r : Range1ul) = + [ struct (r.Min, HalfRangeKind.Left) + if r.Max < UInt64.MaxValue then struct (r.Max + 1UL, HalfRangeKind.Right) ] + + let inline private ofRange (r : Range1ul) = + RangeSet1ul(MapExt.ofListV <| getHalfRanges r) + + let private ofRanges (ranges : seq) = + let halves = + ranges + |> Seq.toList + |> List.collect getHalfRanges + |> List.sortBy fstv + + let mutable level = 0 + let result = ResizeArray() + + for (struct (i, k) as h) in halves do + if k = HalfRangeKind.Left then + if level = 0 then result.Add h + level <- level + 1 + else + level <- level - 1 + if level = 0 then result.Add h + + RangeSet1ul(MapExt.ofSeqV result) /// Builds a range set of the given list of ranges. - let inline ofList (ranges : Range1ul list) = RangeSet1ul.OfSeq ranges + let ofList (ranges : Range1ul list) = + match ranges with + | [] -> empty + | [r] -> ofRange r + | _ -> ofRanges ranges /// Builds a range set of the given array of ranges. - let inline ofArray (ranges : Range1ul[]) = RangeSet1ul.OfSeq ranges + let ofArray (ranges : Range1ul[]) = + if ranges.Length = 0 then empty + elif ranges.Length = 1 then ofRange ranges.[0] + else ofRanges ranges + + /// Builds a range set of the given sequence of ranges. + let inline ofSeq (ranges : seq) = + ofList <| Seq.toList ranges /// Adds the given range to the set. let inline add (range : Range1ul) (set : RangeSet1ul) = set.Add range diff --git a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_template.fs b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_template.fs index bf7d3da6..883dcbce 100644 --- a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_template.fs +++ b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_template.fs @@ -55,21 +55,6 @@ type __rangeset__ internal (store : MapExt<__ltype__, HalfRangeKind>) = /// Empty range set. static member Empty = empty - /// Builds a range set of the given sequence of ranges. - static member OfSeq(ranges : __range__ seq) = - let arr = ranges |> Seq.toArray - if arr.Length = 0 then - empty - elif arr.Length = 1 then - let r = arr.[0] - __rangeset__(MapExt.ofListV [ - struct (r.Min, HalfRangeKind.Left) - if r.Max < __maxvalue__ then struct (r.Max + __one__, HalfRangeKind.Right) - ]) - else - // TODO: better impl possible (sort array and traverse) - arr |> Array.fold (fun s r -> s.Add r) empty - member inline private x.Store = store // We cannot directly describe a range that ends at __maxvalue__ since the right half-range is inserted @@ -411,14 +396,49 @@ module __rangeset__ = /// Returns the total range spanned by the range set, i.e. [min, max]. let inline range (set : __rangeset__) = set.Range - /// Builds a range set of the given sequence of ranges. - let inline ofSeq (ranges : seq<__range__>) = __rangeset__.OfSeq ranges + let inline private getHalfRanges (r : __range__) = + [ struct (r.Min, HalfRangeKind.Left) + if r.Max < __maxvalue__ then struct (r.Max + __one__, HalfRangeKind.Right) ] + + let inline private ofRange (r : __range__) = + __rangeset__(MapExt.ofListV <| getHalfRanges r) + + let private ofRanges (ranges : seq<__range__>) = + let halves = + ranges + |> Seq.toList + |> List.collect getHalfRanges + |> List.sortBy fstv + + let mutable level = 0 + let result = ResizeArray() + + for (struct (i, k) as h) in halves do + if k = HalfRangeKind.Left then + if level = 0 then result.Add h + level <- level + 1 + else + level <- level - 1 + if level = 0 then result.Add h + + __rangeset__(MapExt.ofSeqV result) /// Builds a range set of the given list of ranges. - let inline ofList (ranges : __range__ list) = __rangeset__.OfSeq ranges + let ofList (ranges : __range__ list) = + match ranges with + | [] -> empty + | [r] -> ofRange r + | _ -> ofRanges ranges /// Builds a range set of the given array of ranges. - let inline ofArray (ranges : __range__[]) = __rangeset__.OfSeq ranges + let ofArray (ranges : __range__[]) = + if ranges.Length = 0 then empty + elif ranges.Length = 1 then ofRange ranges.[0] + else ofRanges ranges + + /// Builds a range set of the given sequence of ranges. + let inline ofSeq (ranges : seq<__range__>) = + ofList <| Seq.toList ranges /// Adds the given range to the set. let inline add (range : __range__) (set : __rangeset__) = set.Add range diff --git a/src/Tests/Aardvark.Base.FSharp.Benchmarks/RangeSet.fs b/src/Tests/Aardvark.Base.FSharp.Benchmarks/RangeSet.fs index 54c315f8..aabff5a4 100644 --- a/src/Tests/Aardvark.Base.FSharp.Benchmarks/RangeSet.fs +++ b/src/Tests/Aardvark.Base.FSharp.Benchmarks/RangeSet.fs @@ -185,6 +185,26 @@ module RangeSetTests = (a1 = b1) |> should be True (a2 = b2) |> should be True + [] + let ``[RangeSet] ofList`` () = + let rnd = RandomSystem(0) + + let ranges = + List.init 5 (fun _ -> + let mutable l = Int64.MaxValue + let mutable r = Int64.MinValue + + while l > r do + l <- rnd.UniformLong() + r <- l + rnd.UniformLong(100000L) + + Range1l(l, r) + ) + + let expected = (RangeSet1l.empty, ranges) ||> List.fold (fun s r -> RangeSet1l.add r s) + let actual = RangeSet1l.ofList ranges + actual |> should equal expected + module RangeSetBenchmarks = open BenchmarkDotNet.Attributes; @@ -222,6 +242,15 @@ module RangeSetBenchmarks = setNew <- RangeSet1l.ofList ranges + [] + member x.OfListOld() = + RangeSet64.ofList ranges + + [] + member x.OfListNew() = + RangeSet1l.ofList ranges + + [] member x.InsertOld() = let mutable set = RangeSet64.empty From e0f644142fc9d6d24036ffa74f45e2a2a83d2189 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Oct 2023 09:00:21 +0200 Subject: [PATCH 07/45] [FSharp] Add more ArraySegment functions --- .../Interop/ArraySegment.fs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Aardvark.Base.FSharp/Interop/ArraySegment.fs b/src/Aardvark.Base.FSharp/Interop/ArraySegment.fs index 204a1d63..c982a1aa 100644 --- a/src/Aardvark.Base.FSharp/Interop/ArraySegment.fs +++ b/src/Aardvark.Base.FSharp/Interop/ArraySegment.fs @@ -12,6 +12,18 @@ module ArraySegment = member inline x.Item(index : int) = x.Array.[x.Offset + index] #endif + let inline tryHeadV (segment : ArraySegment<'T>) = + if segment.Count > 0 then ValueSome segment.[0] + else ValueNone + + let inline copyArray (segment : ArraySegment<'T>) = + let arr = Array.zeroCreate<'T> segment.Count + Array.Copy(segment.Array, segment.Offset, arr, 0, arr.Length) + arr + + let inline copy (segment : ArraySegment<'T1>) = + ArraySegment (copyArray segment) + let inline mapArray (mapping : 'T1 -> 'T2) (segment : ArraySegment<'T1>) = Array.init segment.Count (fun i -> mapping segment.[i]) @@ -29,6 +41,18 @@ module ArraySegment = state + let inline reduce (reduction : 'T -> 'T -> 'T) (segment : ArraySegment<'T>) = + if segment.Count = 0 then + invalidArg "segment" "Cannot reduce an empty ArraySegment." + else + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt (reduction) + let mutable res = segment.[0] + + for i = 1 to segment.Count - 1 do + res <- f.Invoke(res, segment.[i]) + + res + /// Forms a slice of the specified length out of the array segment starting at the specified index. let inline slice (start : int) (count : int) (segment : ArraySegment<'T>) = #if NETSTANDARD2_1_OR_GREATER From b96040ec55d979095098e5b773544d908062c7b0 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 16 Oct 2023 09:08:20 +0200 Subject: [PATCH 08/45] [FSharp] Add some utilities --- .../Prelude/FSLibExtensions.fs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Aardvark.Base.FSharp/Prelude/FSLibExtensions.fs b/src/Aardvark.Base.FSharp/Prelude/FSLibExtensions.fs index 53d381e9..1d7fe00a 100644 --- a/src/Aardvark.Base.FSharp/Prelude/FSLibExtensions.fs +++ b/src/Aardvark.Base.FSharp/Prelude/FSLibExtensions.fs @@ -67,6 +67,15 @@ module Prelude = (i + 1), folder i state x ) |> snd + let inline tryPickV chooser (source : seq<'T>) = + use e = source.GetEnumerator() + let mutable res = ValueNone + + while (ValueOption.isNone res && e.MoveNext()) do + res <- chooser e.Current + + res + open System.Collections open System.Collections.Generic @@ -153,6 +162,17 @@ module Prelude = ValueNone search 0 (array.Length - 1) + + let inline tryPickV chooser (array : _[]) = + let rec loop i = + if i >= array.Length then + ValueNone + else + match chooser array.[i] with + | ValueNone -> loop (i + 1) + | res -> res + + loop 0 module Disposable = @@ -161,12 +181,18 @@ module Prelude = let inline dispose v = (^a : (member Dispose : unit -> unit) v) module Option = - let inline defaultValue (fallback : 'a) (option : Option<'a>) = match option with | Some value -> value | None -> fallback + let inline toValueOption (value : 'T option) = + match value with Some x -> ValueSome x | _ -> ValueNone + + module ValueOption = + let inline toOption (value : 'T voption) = + match value with ValueSome x -> Some x | _ -> None + [] module ValueOptionOperators = let inline fstv (struct (x, _)) = x From e1ee757be027d1471fd4d4446197033d678fa9a5 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Oct 2023 16:34:29 +0200 Subject: [PATCH 09/45] [Text] Fix NestedBracketSplitCount --- src/Aardvark.Base/Text/Text.cs | 35 +++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Aardvark.Base/Text/Text.cs b/src/Aardvark.Base/Text/Text.cs index a0607798..677facd6 100644 --- a/src/Aardvark.Base/Text/Text.cs +++ b/src/Aardvark.Base/Text/Text.cs @@ -1254,7 +1254,8 @@ public static string[] ToStringArray(this Text[] textArray) public static List ToListOfString(this Text[] textArray) => textArray.MapToList(t => t.ToString()); - + + [Obsolete("Does not always return the same count as NestedBracketSplit() result length. Use NestedBracketSplitCount2 instead.")] public static int NestedBracketSplitCount(this Text text, int splitLevel) { int count = 0; @@ -1274,6 +1275,38 @@ public static int NestedBracketSplitCount(this Text text, int splitLevel) return count; } + public static int NestedBracketSplitCount2(this Text text, int splitLevel) + { + int count = 0; + int level = 0; + int begin = text.Start; + int end = text.End; + for (int pos = text.Start; pos < end; pos++) + { + switch (text.String[pos]) + { + case '[': + ++level; + if (level == splitLevel) begin = pos + 1; + break; + case ']': + if (level == splitLevel) ++count; + --level; + break; + case ',': + if (level == splitLevel) + { + ++count; + begin = pos + 1; + } + break; + } + } + if (level == splitLevel && begin < end) + ++count; + return count; + } + #endregion #region Text Extensions From b159a1912e11b3b6e1e69a4f234424c44d8fdd6a Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Oct 2023 16:39:12 +0200 Subject: [PATCH 10/45] [Color] Fix overflow issue in C4ui constructors --- src/Aardvark.Base/Math/Colors/Color_auto.cs | 26 +++++++++---------- .../Math/Colors/Color_template.cs | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Aardvark.Base/Math/Colors/Color_auto.cs b/src/Aardvark.Base/Math/Colors/Color_auto.cs index f84f4fd8..b548495e 100644 --- a/src/Aardvark.Base/Math/Colors/Color_auto.cs +++ b/src/Aardvark.Base/Math/Colors/Color_auto.cs @@ -13457,7 +13457,7 @@ public C4us(V3i vec) R = (ushort)(vec.X); G = (ushort)(vec.Y); B = (ushort)(vec.Z); - A = 2^16 - 1; + A = 65535; } /// @@ -13471,7 +13471,7 @@ public C4us(V3ui vec) R = (ushort)(vec.X); G = (ushort)(vec.Y); B = (ushort)(vec.Z); - A = 2^16 - 1; + A = 65535; } /// @@ -13485,7 +13485,7 @@ public C4us(V3l vec) R = (ushort)(vec.X); G = (ushort)(vec.Y); B = (ushort)(vec.Z); - A = 2^16 - 1; + A = 65535; } /// @@ -13499,7 +13499,7 @@ public C4us(V3f vec) R = (ushort)(vec.X); G = (ushort)(vec.Y); B = (ushort)(vec.Z); - A = 2^16 - 1; + A = 65535; } /// @@ -13513,7 +13513,7 @@ public C4us(V3d vec) R = (ushort)(vec.X); G = (ushort)(vec.Y); B = (ushort)(vec.Z); - A = 2^16 - 1; + A = 65535; } /// @@ -15933,7 +15933,7 @@ public C4ui(V3ui vec) R = (vec.X); G = (vec.Y); B = (vec.Z); - A = 2^32 - 1; + A = UInt32.MaxValue; } /// @@ -15947,7 +15947,7 @@ public C4ui(V3l vec) R = (uint)(vec.X); G = (uint)(vec.Y); B = (uint)(vec.Z); - A = 2^32 - 1; + A = UInt32.MaxValue; } /// @@ -15961,7 +15961,7 @@ public C4ui(V3f vec) R = (uint)(vec.X); G = (uint)(vec.Y); B = (uint)(vec.Z); - A = 2^32 - 1; + A = UInt32.MaxValue; } /// @@ -15975,7 +15975,7 @@ public C4ui(V3d vec) R = (uint)(vec.X); G = (uint)(vec.Y); B = (uint)(vec.Z); - A = 2^32 - 1; + A = UInt32.MaxValue; } /// @@ -18241,7 +18241,7 @@ public C4f(V3f vec) R = (vec.X); G = (vec.Y); B = (vec.Z); - A = 1; + A = 1.0f; } /// @@ -18254,7 +18254,7 @@ public C4f(V3d vec) R = (float)(vec.X); G = (float)(vec.Y); B = (float)(vec.Z); - A = 1; + A = 1.0f; } /// @@ -20453,7 +20453,7 @@ public C4d(V3f vec) R = (double)(vec.X); G = (double)(vec.Y); B = (double)(vec.Z); - A = 1; + A = 1.0; } /// @@ -20466,7 +20466,7 @@ public C4d(V3d vec) R = (vec.X); G = (vec.Y); B = (vec.Z); - A = 1; + A = 1.0; } /// diff --git a/src/Aardvark.Base/Math/Colors/Color_template.cs b/src/Aardvark.Base/Math/Colors/Color_template.cs index 3a84f871..1bf7ca2f 100644 --- a/src/Aardvark.Base/Math/Colors/Color_template.cs +++ b/src/Aardvark.Base/Math/Colors/Color_template.cs @@ -510,7 +510,7 @@ public __type__(__t1.Name__ color, __ftype__ alpha) public __type__(__vt.Name__ vec) { //# fields.ForEach(Meta.VecFields, (c, vf, i) => { - __c__ = /*# if (i < d) { */__convert__(vec.__vf__);/*# } else {*/__maxval__;/*# }*/ + __c__ = /*# if (i < d) { */__convert__(vec.__vf__);/*# } else {*/__t.MaxValue__;/*# }*/ //# }); } From 05021b82bf570395f66e3443a8c24adc1041792d Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Oct 2023 17:43:18 +0200 Subject: [PATCH 11/45] [PixImage] Fix leak in Windows Media loader Trying to load a file directly leads to a handle leak in case of failure. Reproducible in PGM loader test. --- src/Aardvark.PixImage.WindowsMedia/PixImageWindowsMedia.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Aardvark.PixImage.WindowsMedia/PixImageWindowsMedia.cs b/src/Aardvark.PixImage.WindowsMedia/PixImageWindowsMedia.cs index 3325f3fc..e65bcd9d 100644 --- a/src/Aardvark.PixImage.WindowsMedia/PixImageWindowsMedia.cs +++ b/src/Aardvark.PixImage.WindowsMedia/PixImageWindowsMedia.cs @@ -240,12 +240,13 @@ private class PixLoader : IPixLoader /// public PixImage LoadFromFile(string filename) { + using var stream = new MemoryStream(File.ReadAllBytes(filename)); + var bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); - bitmapImage.UriSource = new Uri(filename, UriKind.RelativeOrAbsolute); + bitmapImage.StreamSource = stream; bitmapImage.CacheOption = BitmapCacheOption.OnLoad; - bitmapImage.CreateOptions = BitmapCreateOptions.None - | BitmapCreateOptions.PreservePixelFormat; + bitmapImage.CreateOptions = BitmapCreateOptions.PreservePixelFormat; bitmapImage.EndInit(); return CreateFromBitmapSource(bitmapImage); } From 62889e7324e3a88969ba490625e19a7cdb2ff923 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Oct 2023 17:57:08 +0200 Subject: [PATCH 12/45] [Color] Improve parsing This commit improves parsing colors from strings: - Adds support for parsing hexadecimal color strings - Adds TryParse that does not throw exceptions on failure --- src/Aardvark.Base/Math/Colors/Color.cs | 93 ++ src/Aardvark.Base/Math/Colors/Color_auto.cs | 1154 ++++++++++++++--- .../Math/Colors/Color_template.cs | 124 +- .../Aardvark.Base.Tests/Math/ColorTests.cs | 74 ++ 4 files changed, 1266 insertions(+), 179 deletions(-) diff --git a/src/Aardvark.Base/Math/Colors/Color.cs b/src/Aardvark.Base/Math/Colors/Color.cs index de86b1bf..3b31efd1 100644 --- a/src/Aardvark.Base/Math/Colors/Color.cs +++ b/src/Aardvark.Base/Math/Colors/Color.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; +using System.Text.RegularExpressions; namespace Aardvark.Base { @@ -1900,6 +1902,97 @@ public static C4f Average(this IEnumerable items) } #endregion + + #region Hex Parsing + + // See: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color + private static readonly Regex regexHexSgl = new Regex("^(?:#|0x)?(?[0-9a-fA-F])(?[0-9a-fA-F])(?[0-9a-fA-F])(?[0-9a-fA-F])?$", RegexOptions.Compiled); + private static readonly Regex regexHexDbl = new Regex("^(?:#|0x)?(?[0-9a-fA-F]{2})(?[0-9a-fA-F]{2})(?[0-9a-fA-F]{2})(?[0-9a-fA-F]{2})?$", RegexOptions.Compiled); + + private static bool TryParseHex(Regex regex, string input, out C4b result) + { + var m = regex.Match(input); + if (m.Success) + { + string[] values = new string[4]; + values[0] = m.Groups["R"].Value; + values[1] = m.Groups["G"].Value; + values[2] = m.Groups["B"].Value; + values[3] = m.Groups["A"].Success ? m.Groups["A"].Value : "FF"; + + result = + new C4b(values.Map((x) => { + var value = x.Length == 1 ? new string(x[0], 2) : x; + return byte.Parse(value, NumberStyles.AllowHexSpecifier); + })); + + return true; + } + + result = C4b.Zero; + return false; + } + + /// + /// Parses a hexadecimal color string with format RRGGBBAA or RGBA, where the alpha component is optional. + /// + /// + /// For the single digit RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// The color string may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C4b.Zero otherwise. + /// True on success, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseHex(Text input, out C4b result) + { + var str = input.WhiteSpaceTrimmed.ToString(); + return TryParseHex(regexHexDbl, str, out result) || TryParseHex(regexHexSgl, str, out result); + } + + /// + /// Parses a hexadecimal color string with format RRGGBBAA or RGBA, where the alpha component is optional. + /// + /// + /// For the single digit RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// The color string may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C4b.Zero otherwise. + /// True on success, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseHex(string input, out C4b result) + => TryParseHex(new Text(input), out result); + + /// + /// Parses a hexadecimal color string with format RRGGBBAA or RGBA, where the alpha component is optional. + /// + /// + /// For the single digit RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// The color string may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid hexadecimal color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static C4b ParseHex(Text input) + => TryParseHex(input, out C4b result) ? result : throw new FormatException($"{input} is not a valid hexadecimal color."); + + /// + /// Parses a hexadecimal color string with format RRGGBBAA or RGBA, where the alpha component is optional. + /// + /// + /// For the single digit RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// The color string may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid hexadecimal color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static C4b ParseHex(string input) + => ParseHex(new Text(input)); + + #endregion } #endregion diff --git a/src/Aardvark.Base/Math/Colors/Color_auto.cs b/src/Aardvark.Base/Math/Colors/Color_auto.cs index b548495e..5ce6fdb1 100644 --- a/src/Aardvark.Base/Math/Colors/Color_auto.cs +++ b/src/Aardvark.Base/Math/Colors/Color_auto.cs @@ -1594,30 +1594,102 @@ public static C3b DivideByInt(C3b c, int x) #region Parsing - public static C3b Parse(string s, IFormatProvider provider) + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C3b.Zero otherwise. + /// True on success, false otherwise. + public static bool TryParse(Text t, out C3b result) { - return Parse(s); + if (Col.TryParseHex(t, out C4b tmp)) + { + result = tmp.ToC3b(); + return true; + } + else + { + bool success = true; + byte[] values = new byte[4] { 255, 255, 255, 255 }; + + byte parse(Text t) + { + if (!byte.TryParse(t.ToString(), out byte value)) + success = false; + + return value; + }; + + var count = t.NestedBracketSplitCount2(1); + if (count == 3 || count == 4) + t.NestedBracketSplit(1, parse, () => values); + else + success = false; + + result = success ? new C3b(values) : Zero; + return success; + } } + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C3b.Zero otherwise. + /// True on success, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParse(string s, out C3b result) + => TryParse(new Text(s), out result); + + [Obsolete("Parameter provider is unused.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static C3b Parse(string s, IFormatProvider provider) + => Parse(s); + + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C3b color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3b Parse(string s) - { - var x = s.NestedBracketSplitLevelOne().ToArray(); - return new C3b( - byte.Parse(x[0], CultureInfo.InvariantCulture), - byte.Parse(x[1], CultureInfo.InvariantCulture), - byte.Parse(x[2], CultureInfo.InvariantCulture) - ); - } + => Parse(new Text(s)); + [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3b Parse(Text t, int bracketLevel = 1) - { - return t.NestedBracketSplit(bracketLevel, Text.Parse, C3b.Setter); - } + => t.NestedBracketSplit(bracketLevel, Text.Parse, C3b.Setter); + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C3b color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3b Parse(Text t) - { - return t.NestedBracketSplit(1, Text.Parse, C3b.Setter); - } + => TryParse(t, out C3b result) ? result : throw new FormatException($"{t} is not a valid C3b color."); #endregion @@ -1724,6 +1796,17 @@ public static bool IsTiny(this C3b c, byte epsilon) public static partial class Col { + #region ToHexString + + /// + /// Returns the hexadecimal representation with format RRGGBB. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToHexString(this C3b c) + => $"{c.R:X2}{c.G:X2}{c.B:X2}"; + + #endregion + #region Comparisons /// @@ -3786,30 +3869,102 @@ public static C3us DivideByInt(C3us c, int x) #region Parsing - public static C3us Parse(string s, IFormatProvider provider) + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C3us.Zero otherwise. + /// True on success, false otherwise. + public static bool TryParse(Text t, out C3us result) { - return Parse(s); + if (Col.TryParseHex(t, out C4b tmp)) + { + result = tmp.ToC3us(); + return true; + } + else + { + bool success = true; + ushort[] values = new ushort[4] { 65535, 65535, 65535, 65535 }; + + ushort parse(Text t) + { + if (!ushort.TryParse(t.ToString(), out ushort value)) + success = false; + + return value; + }; + + var count = t.NestedBracketSplitCount2(1); + if (count == 3 || count == 4) + t.NestedBracketSplit(1, parse, () => values); + else + success = false; + + result = success ? new C3us(values) : Zero; + return success; + } } + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C3us.Zero otherwise. + /// True on success, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParse(string s, out C3us result) + => TryParse(new Text(s), out result); + + [Obsolete("Parameter provider is unused.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static C3us Parse(string s, IFormatProvider provider) + => Parse(s); + + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C3us color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3us Parse(string s) - { - var x = s.NestedBracketSplitLevelOne().ToArray(); - return new C3us( - ushort.Parse(x[0], CultureInfo.InvariantCulture), - ushort.Parse(x[1], CultureInfo.InvariantCulture), - ushort.Parse(x[2], CultureInfo.InvariantCulture) - ); - } + => Parse(new Text(s)); + [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3us Parse(Text t, int bracketLevel = 1) - { - return t.NestedBracketSplit(bracketLevel, Text.Parse, C3us.Setter); - } + => t.NestedBracketSplit(bracketLevel, Text.Parse, C3us.Setter); + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C3us color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3us Parse(Text t) - { - return t.NestedBracketSplit(1, Text.Parse, C3us.Setter); - } + => TryParse(t, out C3us result) ? result : throw new FormatException($"{t} is not a valid C3us color."); #endregion @@ -3916,6 +4071,17 @@ public static bool IsTiny(this C3us c, ushort epsilon) public static partial class Col { + #region ToHexString + + /// + /// Returns the hexadecimal representation with format RRGGBB. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToHexString(this C3us c) + => c.ToC3b().ToHexString(); + + #endregion + #region Comparisons /// @@ -5901,30 +6067,102 @@ public static C3ui DivideByInt(C3ui c, int x) #region Parsing - public static C3ui Parse(string s, IFormatProvider provider) + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C3ui.Zero otherwise. + /// True on success, false otherwise. + public static bool TryParse(Text t, out C3ui result) { - return Parse(s); + if (Col.TryParseHex(t, out C4b tmp)) + { + result = tmp.ToC3ui(); + return true; + } + else + { + bool success = true; + uint[] values = new uint[4] { UInt32.MaxValue, UInt32.MaxValue, UInt32.MaxValue, UInt32.MaxValue }; + + uint parse(Text t) + { + if (!uint.TryParse(t.ToString(), out uint value)) + success = false; + + return value; + }; + + var count = t.NestedBracketSplitCount2(1); + if (count == 3 || count == 4) + t.NestedBracketSplit(1, parse, () => values); + else + success = false; + + result = success ? new C3ui(values) : Zero; + return success; + } } + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C3ui.Zero otherwise. + /// True on success, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParse(string s, out C3ui result) + => TryParse(new Text(s), out result); + + [Obsolete("Parameter provider is unused.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static C3ui Parse(string s, IFormatProvider provider) + => Parse(s); + + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C3ui color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3ui Parse(string s) - { - var x = s.NestedBracketSplitLevelOne().ToArray(); - return new C3ui( - uint.Parse(x[0], CultureInfo.InvariantCulture), - uint.Parse(x[1], CultureInfo.InvariantCulture), - uint.Parse(x[2], CultureInfo.InvariantCulture) - ); - } + => Parse(new Text(s)); + [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3ui Parse(Text t, int bracketLevel = 1) - { - return t.NestedBracketSplit(bracketLevel, Text.Parse, C3ui.Setter); - } + => t.NestedBracketSplit(bracketLevel, Text.Parse, C3ui.Setter); + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C3ui color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3ui Parse(Text t) - { - return t.NestedBracketSplit(1, Text.Parse, C3ui.Setter); - } + => TryParse(t, out C3ui result) ? result : throw new FormatException($"{t} is not a valid C3ui color."); #endregion @@ -6031,6 +6269,17 @@ public static bool IsTiny(this C3ui c, uint epsilon) public static partial class Col { + #region ToHexString + + /// + /// Returns the hexadecimal representation with format RRGGBB. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToHexString(this C3ui c) + => c.ToC3b().ToHexString(); + + #endregion + #region Comparisons /// @@ -7891,30 +8140,102 @@ public static C3f DivideByInt(C3f c, int x) #region Parsing - public static C3f Parse(string s, IFormatProvider provider) + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C3f.Zero otherwise. + /// True on success, false otherwise. + public static bool TryParse(Text t, out C3f result) { - return Parse(s); + if (Col.TryParseHex(t, out C4b tmp)) + { + result = tmp.ToC3f(); + return true; + } + else + { + bool success = true; + float[] values = new float[4] { 1.0f, 1.0f, 1.0f, 1.0f }; + + float parse(Text t) + { + if (!float.TryParse(t.ToString(), out float value)) + success = false; + + return value; + }; + + var count = t.NestedBracketSplitCount2(1); + if (count == 3 || count == 4) + t.NestedBracketSplit(1, parse, () => values); + else + success = false; + + result = success ? new C3f(values) : Zero; + return success; + } } + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C3f.Zero otherwise. + /// True on success, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParse(string s, out C3f result) + => TryParse(new Text(s), out result); + + [Obsolete("Parameter provider is unused.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static C3f Parse(string s, IFormatProvider provider) + => Parse(s); + + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C3f color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3f Parse(string s) - { - var x = s.NestedBracketSplitLevelOne().ToArray(); - return new C3f( - float.Parse(x[0], CultureInfo.InvariantCulture), - float.Parse(x[1], CultureInfo.InvariantCulture), - float.Parse(x[2], CultureInfo.InvariantCulture) - ); - } + => Parse(new Text(s)); + [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3f Parse(Text t, int bracketLevel = 1) - { - return t.NestedBracketSplit(bracketLevel, Text.Parse, C3f.Setter); - } + => t.NestedBracketSplit(bracketLevel, Text.Parse, C3f.Setter); + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C3f color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3f Parse(Text t) - { - return t.NestedBracketSplit(1, Text.Parse, C3f.Setter); - } + => TryParse(t, out C3f result) ? result : throw new FormatException($"{t} is not a valid C3f color."); #endregion @@ -8064,6 +8385,17 @@ public static bool IsFinite(C3f c) public static partial class Col { + #region ToHexString + + /// + /// Returns the hexadecimal representation with format RRGGBB. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToHexString(this C3f c) + => c.ToC3b().ToHexString(); + + #endregion + #region Comparisons /// @@ -9918,30 +10250,102 @@ public static C3d DivideByInt(C3d c, int x) #region Parsing - public static C3d Parse(string s, IFormatProvider provider) + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C3d.Zero otherwise. + /// True on success, false otherwise. + public static bool TryParse(Text t, out C3d result) { - return Parse(s); + if (Col.TryParseHex(t, out C4b tmp)) + { + result = tmp.ToC3d(); + return true; + } + else + { + bool success = true; + double[] values = new double[4] { 1.0, 1.0, 1.0, 1.0 }; + + double parse(Text t) + { + if (!double.TryParse(t.ToString(), out double value)) + success = false; + + return value; + }; + + var count = t.NestedBracketSplitCount2(1); + if (count == 3 || count == 4) + t.NestedBracketSplit(1, parse, () => values); + else + success = false; + + result = success ? new C3d(values) : Zero; + return success; + } } + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C3d.Zero otherwise. + /// True on success, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParse(string s, out C3d result) + => TryParse(new Text(s), out result); + + [Obsolete("Parameter provider is unused.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static C3d Parse(string s, IFormatProvider provider) + => Parse(s); + + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C3d color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3d Parse(string s) - { - var x = s.NestedBracketSplitLevelOne().ToArray(); - return new C3d( - double.Parse(x[0], CultureInfo.InvariantCulture), - double.Parse(x[1], CultureInfo.InvariantCulture), - double.Parse(x[2], CultureInfo.InvariantCulture) - ); - } + => Parse(new Text(s)); + [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3d Parse(Text t, int bracketLevel = 1) - { - return t.NestedBracketSplit(bracketLevel, Text.Parse, C3d.Setter); - } + => t.NestedBracketSplit(bracketLevel, Text.Parse, C3d.Setter); + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional and discarded. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C3d color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3d Parse(Text t) - { - return t.NestedBracketSplit(1, Text.Parse, C3d.Setter); - } + => TryParse(t, out C3d result) ? result : throw new FormatException($"{t} is not a valid C3d color."); #endregion @@ -10091,6 +10495,17 @@ public static bool IsFinite(C3d c) public static partial class Col { + #region ToHexString + + /// + /// Returns the hexadecimal representation with format RRGGBB. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToHexString(this C3d c) + => c.ToC3b().ToHexString(); + + #endregion + #region Comparisons /// @@ -12466,31 +12881,101 @@ public static C4b DivideByInt(C4b c, int x) #region Parsing - public static C4b Parse(string s, IFormatProvider provider) + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C4b.Zero otherwise. + /// True on success, false otherwise. + public static bool TryParse(Text t, out C4b result) { - return Parse(s); + if (Col.TryParseHex(t, out result)) + { + return true; + } + else + { + bool success = true; + byte[] values = new byte[4] { 255, 255, 255, 255 }; + + byte parse(Text t) + { + if (!byte.TryParse(t.ToString(), out byte value)) + success = false; + + return value; + }; + + var count = t.NestedBracketSplitCount2(1); + if (count == 3 || count == 4) + t.NestedBracketSplit(1, parse, () => values); + else + success = false; + + result = success ? new C4b(values) : Zero; + return success; + } } + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C4b.Zero otherwise. + /// True on success, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParse(string s, out C4b result) + => TryParse(new Text(s), out result); + + [Obsolete("Parameter provider is unused.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static C4b Parse(string s, IFormatProvider provider) + => Parse(s); + + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C4b color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4b Parse(string s) - { - var x = s.NestedBracketSplitLevelOne().ToArray(); - return new C4b( - byte.Parse(x[0], CultureInfo.InvariantCulture), - byte.Parse(x[1], CultureInfo.InvariantCulture), - byte.Parse(x[2], CultureInfo.InvariantCulture), - byte.Parse(x[3], CultureInfo.InvariantCulture) - ); - } + => Parse(new Text(s)); + [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4b Parse(Text t, int bracketLevel = 1) - { - return t.NestedBracketSplit(bracketLevel, Text.Parse, C4b.Setter); - } + => t.NestedBracketSplit(bracketLevel, Text.Parse, C4b.Setter); + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C4b color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4b Parse(Text t) - { - return t.NestedBracketSplit(1, Text.Parse, C4b.Setter); - } + => TryParse(t, out C4b result) ? result : throw new FormatException($"{t} is not a valid C4b color."); #endregion @@ -12608,6 +13093,17 @@ public static bool IsTiny(this C4b c, byte epsilon) public static partial class Col { + #region ToHexString + + /// + /// Returns the hexadecimal representation with format RRGGBBAA. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToHexString(this C4b c) + => $"{c.R:X2}{c.G:X2}{c.B:X2}{c.A:X2}"; + + #endregion + #region Comparisons /// @@ -14943,31 +15439,102 @@ public static C4us DivideByInt(C4us c, int x) #region Parsing - public static C4us Parse(string s, IFormatProvider provider) + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C4us.Zero otherwise. + /// True on success, false otherwise. + public static bool TryParse(Text t, out C4us result) { - return Parse(s); + if (Col.TryParseHex(t, out C4b tmp)) + { + result = tmp.ToC4us(); + return true; + } + else + { + bool success = true; + ushort[] values = new ushort[4] { 65535, 65535, 65535, 65535 }; + + ushort parse(Text t) + { + if (!ushort.TryParse(t.ToString(), out ushort value)) + success = false; + + return value; + }; + + var count = t.NestedBracketSplitCount2(1); + if (count == 3 || count == 4) + t.NestedBracketSplit(1, parse, () => values); + else + success = false; + + result = success ? new C4us(values) : Zero; + return success; + } } + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C4us.Zero otherwise. + /// True on success, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParse(string s, out C4us result) + => TryParse(new Text(s), out result); + + [Obsolete("Parameter provider is unused.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static C4us Parse(string s, IFormatProvider provider) + => Parse(s); + + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C4us color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4us Parse(string s) - { - var x = s.NestedBracketSplitLevelOne().ToArray(); - return new C4us( - ushort.Parse(x[0], CultureInfo.InvariantCulture), - ushort.Parse(x[1], CultureInfo.InvariantCulture), - ushort.Parse(x[2], CultureInfo.InvariantCulture), - ushort.Parse(x[3], CultureInfo.InvariantCulture) - ); - } + => Parse(new Text(s)); + [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4us Parse(Text t, int bracketLevel = 1) - { - return t.NestedBracketSplit(bracketLevel, Text.Parse, C4us.Setter); - } + => t.NestedBracketSplit(bracketLevel, Text.Parse, C4us.Setter); + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C4us color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4us Parse(Text t) - { - return t.NestedBracketSplit(1, Text.Parse, C4us.Setter); - } + => TryParse(t, out C4us result) ? result : throw new FormatException($"{t} is not a valid C4us color."); #endregion @@ -15085,6 +15652,17 @@ public static bool IsTiny(this C4us c, ushort epsilon) public static partial class Col { + #region ToHexString + + /// + /// Returns the hexadecimal representation with format RRGGBBAA. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToHexString(this C4us c) + => c.ToC4b().ToHexString(); + + #endregion + #region Comparisons /// @@ -17325,31 +17903,102 @@ public static C4ui DivideByInt(C4ui c, int x) #region Parsing - public static C4ui Parse(string s, IFormatProvider provider) + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C4ui.Zero otherwise. + /// True on success, false otherwise. + public static bool TryParse(Text t, out C4ui result) { - return Parse(s); + if (Col.TryParseHex(t, out C4b tmp)) + { + result = tmp.ToC4ui(); + return true; + } + else + { + bool success = true; + uint[] values = new uint[4] { UInt32.MaxValue, UInt32.MaxValue, UInt32.MaxValue, UInt32.MaxValue }; + + uint parse(Text t) + { + if (!uint.TryParse(t.ToString(), out uint value)) + success = false; + + return value; + }; + + var count = t.NestedBracketSplitCount2(1); + if (count == 3 || count == 4) + t.NestedBracketSplit(1, parse, () => values); + else + success = false; + + result = success ? new C4ui(values) : Zero; + return success; + } } + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C4ui.Zero otherwise. + /// True on success, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParse(string s, out C4ui result) + => TryParse(new Text(s), out result); + + [Obsolete("Parameter provider is unused.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static C4ui Parse(string s, IFormatProvider provider) + => Parse(s); + + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C4ui color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4ui Parse(string s) - { - var x = s.NestedBracketSplitLevelOne().ToArray(); - return new C4ui( - uint.Parse(x[0], CultureInfo.InvariantCulture), - uint.Parse(x[1], CultureInfo.InvariantCulture), - uint.Parse(x[2], CultureInfo.InvariantCulture), - uint.Parse(x[3], CultureInfo.InvariantCulture) - ); - } + => Parse(new Text(s)); + [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4ui Parse(Text t, int bracketLevel = 1) - { - return t.NestedBracketSplit(bracketLevel, Text.Parse, C4ui.Setter); - } + => t.NestedBracketSplit(bracketLevel, Text.Parse, C4ui.Setter); + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C4ui color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4ui Parse(Text t) - { - return t.NestedBracketSplit(1, Text.Parse, C4ui.Setter); - } + => TryParse(t, out C4ui result) ? result : throw new FormatException($"{t} is not a valid C4ui color."); #endregion @@ -17467,6 +18116,17 @@ public static bool IsTiny(this C4ui c, uint epsilon) public static partial class Col { + #region ToHexString + + /// + /// Returns the hexadecimal representation with format RRGGBBAA. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToHexString(this C4ui c) + => c.ToC4b().ToHexString(); + + #endregion + #region Comparisons /// @@ -19497,31 +20157,102 @@ public static C4f DivideByInt(C4f c, int x) #region Parsing - public static C4f Parse(string s, IFormatProvider provider) + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C4f.Zero otherwise. + /// True on success, false otherwise. + public static bool TryParse(Text t, out C4f result) { - return Parse(s); + if (Col.TryParseHex(t, out C4b tmp)) + { + result = tmp.ToC4f(); + return true; + } + else + { + bool success = true; + float[] values = new float[4] { 1.0f, 1.0f, 1.0f, 1.0f }; + + float parse(Text t) + { + if (!float.TryParse(t.ToString(), out float value)) + success = false; + + return value; + }; + + var count = t.NestedBracketSplitCount2(1); + if (count == 3 || count == 4) + t.NestedBracketSplit(1, parse, () => values); + else + success = false; + + result = success ? new C4f(values) : Zero; + return success; + } } + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C4f.Zero otherwise. + /// True on success, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParse(string s, out C4f result) + => TryParse(new Text(s), out result); + + [Obsolete("Parameter provider is unused.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static C4f Parse(string s, IFormatProvider provider) + => Parse(s); + + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C4f color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4f Parse(string s) - { - var x = s.NestedBracketSplitLevelOne().ToArray(); - return new C4f( - float.Parse(x[0], CultureInfo.InvariantCulture), - float.Parse(x[1], CultureInfo.InvariantCulture), - float.Parse(x[2], CultureInfo.InvariantCulture), - float.Parse(x[3], CultureInfo.InvariantCulture) - ); - } + => Parse(new Text(s)); + [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4f Parse(Text t, int bracketLevel = 1) - { - return t.NestedBracketSplit(bracketLevel, Text.Parse, C4f.Setter); - } + => t.NestedBracketSplit(bracketLevel, Text.Parse, C4f.Setter); + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C4f color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4f Parse(Text t) - { - return t.NestedBracketSplit(1, Text.Parse, C4f.Setter); - } + => TryParse(t, out C4f result) ? result : throw new FormatException($"{t} is not a valid C4f color."); #endregion @@ -19682,6 +20413,17 @@ public static bool IsFinite(C4f c) public static partial class Col { + #region ToHexString + + /// + /// Returns the hexadecimal representation with format RRGGBBAA. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToHexString(this C4f c) + => c.ToC4b().ToHexString(); + + #endregion + #region Comparisons /// @@ -21709,31 +22451,102 @@ public static C4d DivideByInt(C4d c, int x) #region Parsing - public static C4d Parse(string s, IFormatProvider provider) + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C4d.Zero otherwise. + /// True on success, false otherwise. + public static bool TryParse(Text t, out C4d result) { - return Parse(s); + if (Col.TryParseHex(t, out C4b tmp)) + { + result = tmp.ToC4d(); + return true; + } + else + { + bool success = true; + double[] values = new double[4] { 1.0, 1.0, 1.0, 1.0 }; + + double parse(Text t) + { + if (!double.TryParse(t.ToString(), out double value)) + success = false; + + return value; + }; + + var count = t.NestedBracketSplitCount2(1); + if (count == 3 || count == 4) + t.NestedBracketSplit(1, parse, () => values); + else + success = false; + + result = success ? new C4d(values) : Zero; + return success; + } } + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, C4d.Zero otherwise. + /// True on success, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParse(string s, out C4d result) + => TryParse(new Text(s), out result); + + [Obsolete("Parameter provider is unused.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static C4d Parse(string s, IFormatProvider provider) + => Parse(s); + + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C4d color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4d Parse(string s) - { - var x = s.NestedBracketSplitLevelOne().ToArray(); - return new C4d( - double.Parse(x[0], CultureInfo.InvariantCulture), - double.Parse(x[1], CultureInfo.InvariantCulture), - double.Parse(x[2], CultureInfo.InvariantCulture), - double.Parse(x[3], CultureInfo.InvariantCulture) - ); - } + => Parse(new Text(s)); + [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4d Parse(Text t, int bracketLevel = 1) - { - return t.NestedBracketSplit(bracketLevel, Text.Parse, C4d.Setter); - } + => t.NestedBracketSplit(bracketLevel, Text.Parse, C4d.Setter); + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid C4d color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4d Parse(Text t) - { - return t.NestedBracketSplit(1, Text.Parse, C4d.Setter); - } + => TryParse(t, out C4d result) ? result : throw new FormatException($"{t} is not a valid C4d color."); #endregion @@ -21894,6 +22707,17 @@ public static bool IsFinite(C4d c) public static partial class Col { + #region ToHexString + + /// + /// Returns the hexadecimal representation with format RRGGBBAA. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToHexString(this C4d c) + => c.ToC4b().ToHexString(); + + #endregion + #region Comparisons /// diff --git a/src/Aardvark.Base/Math/Colors/Color_template.cs b/src/Aardvark.Base/Math/Colors/Color_template.cs index 1bf7ca2f..8d7182b0 100644 --- a/src/Aardvark.Base/Math/Colors/Color_template.cs +++ b/src/Aardvark.Base/Math/Colors/Color_template.cs @@ -1133,28 +1133,109 @@ public static __type__ DivideByInt(__type__ c, int x) #region Parsing - public static __type__ Parse(string s, IFormatProvider provider) + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional/*# if (!t.HasAlpha) {*/ and discarded/*#}*/. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, __type__.Zero otherwise. + /// True on success, false otherwise. + public static bool TryParse(Text t, out __type__ result) { - return Parse(s); + //# if (type == "C4b") { + if (Col.TryParseHex(t, out result)) + { + return true; + } + //# } else { + if (Col.TryParseHex(t, out C4b tmp)) + { + result = tmp.To__type__(); + return true; + } + //# } + else + { + bool success = true; + __ftype__[] values = new __ftype__[4] { /*# 4.ForEach(p => { */__t.MaxValue__/*# }, comma);*/ }; + + __ftype__ parse(Text t) + { + if (!__ftype__.TryParse(t.ToString(), out __ftype__ value)) + success = false; + + return value; + }; + + var count = t.NestedBracketSplitCount2(1); + if (count == 3 || count == 4) + t.NestedBracketSplit(1, parse, () => values); + else + success = false; + + result = success ? new __type__(values) : Zero; + return success; + } } + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional/*# if (!t.HasAlpha) {*/ and discarded/*#}*/. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// Contains the parsed color on success, __type__.Zero otherwise. + /// True on success, false otherwise. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParse(string s, out __type__ result) + => TryParse(new Text(s), out result); + + [Obsolete("Parameter provider is unused.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static __type__ Parse(string s, IFormatProvider provider) + => Parse(s); + + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional/*# if (!t.HasAlpha) {*/ and discarded/*#}*/. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid __type__ color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static __type__ Parse(string s) - { - var x = s.NestedBracketSplitLevelOne().ToArray(); - return new __type__(/*# t.Len.ForEach(p => { */ - __ftype__.Parse(x[__p__], CultureInfo.InvariantCulture)/*# }, comma); */ - ); - } + => Parse(new Text(s)); + [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static __type__ Parse(Text t, int bracketLevel = 1) - { - return t.NestedBracketSplit(bracketLevel, Text<__ftype__>.Parse, __type__.Setter); - } + => t.NestedBracketSplit(bracketLevel, Text<__ftype__>.Parse, __type__.Setter); + /// + /// Parses a color string with decimal format [R, G, B, A], or hexadecimal formats RRGGBBAA or RGBA. + /// + /// + /// The alpha component in any format is optional/*# if (!t.HasAlpha) {*/ and discarded/*#}*/. + /// For the single digit hexadecimal RGBA format, the components are duplicated (e.g. "F" is interpreted as "FF"). + /// Color strings in a hexadecimal format may be prefixed by "#" or "0x". + /// + /// The string to be parsed. + /// The parsed color. + /// the input does not represent a valid __type__ color. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static __type__ Parse(Text t) - { - return t.NestedBracketSplit(1, Text<__ftype__>.Parse, __type__.Setter); - } + => TryParse(t, out __type__ result) ? result : throw new FormatException($"{t} is not a valid __type__ color."); #endregion @@ -1336,6 +1417,21 @@ public static bool IsFinite(__type__ c) public static partial class Col { + #region ToHexString + + /// + /// Returns the hexadecimal representation with format RRGGBB/*# if (t.HasAlpha) {*/AA/*#}*/. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string ToHexString(this __type__ c) + //# if (ft == Meta.ByteType) { + => /*# if (t.HasAlpha) {*/$"{c.R:X2}{c.G:X2}{c.B:X2}{c.A:X2}"/*# } else {*/$"{c.R:X2}{c.G:X2}{c.B:X2}"/*#}*/; + //# } else { + => c.ToC__t.Len__b().ToHexString(); + //# } + + #endregion + #region Comparisons //# var bops = new[,] { { "<", "Smaller" }, { ">" , "Greater"}, diff --git a/src/Tests/Aardvark.Base.Tests/Math/ColorTests.cs b/src/Tests/Aardvark.Base.Tests/Math/ColorTests.cs index 25eb4616..bd95b80c 100644 --- a/src/Tests/Aardvark.Base.Tests/Math/ColorTests.cs +++ b/src/Tests/Aardvark.Base.Tests/Math/ColorTests.cs @@ -171,5 +171,79 @@ public void ScalarOperators() Assert.AreEqual(new C4b(scalar, scalar, scalar, scalar) / color, scalar / color); } } + + [Test] + public void Parse() + { + var rnd = new RandomSystem(0); + + for (int i = 0; i < 100; i++) + { + var c = rnd.UniformC4ui(); + var s1 = $"[{c.R}, {c.G}, {c.B}, {c.A}]"; + var s2 = $"[{c.R}, {c.G}, {c.B}]"; + + Assert.AreEqual(c.RGB, C3ui.Parse(s1)); + Assert.AreEqual(c, C4ui.Parse(s1)); + Assert.AreEqual(c.RGB, C3ui.Parse(s2)); + Assert.AreEqual(new C4ui(c.RGB, uint.MaxValue), C4ui.Parse(s2)); + } + } + + [Test] + public void ParseTooManyComponents() + { + var t1 = new Text("[1,2,3"); + var c1 = t1.NestedBracketSplitCount2(1); + var r1 = t1.NestedBracketSplit(1).ToArray(); + + var t2 = new Text("[42]"); + var c2 = t2.NestedBracketSplitCount2(1); + var r2 = t2.NestedBracketSplit(1).ToArray(); + + var t3 = new Text("[42,43]"); + var c3 = t3.NestedBracketSplitCount2(1); + var r3 = t3.NestedBracketSplit(1).ToArray(); + + Assert.AreEqual(r1.Length, c1); + Assert.AreEqual(r2.Length, c2); + Assert.AreEqual(r3.Length, c3); + + Assert.IsFalse(C4d.TryParse("[1, 2, 3, 4, 5]", out C4d _)); + } + + [Test] + public void ParseHex() + { + var rnd = new RandomSystem(1); + + for (int i = 0; i < 100; i++) + { + var color = rnd.UniformC4d().ToC4b(); + var str = color.ToHexString(); + + Assert.IsTrue(Col.TryParseHex(str, out C4b result)); + Assert.IsTrue(C4b.TryParse(str, out C4b result2)); + Assert.AreEqual(color, result); + Assert.AreEqual(result, result2); + } + } + + [Test] + public void ParseHexSingleDigit() + { + var rnd = new RandomSystem(1); + + for (int i = 0; i < 100; i++) + { + var str = rnd.UniformC4d().ToHexString(); + var dbl = $"#{str[0]}{str[0]}{str[2]}{str[2]}{str[4]}{str[4]}"; + var sgl = $"0x{str[0]}{str[2]}{str[4]}F"; + + Assert.IsTrue(Col.TryParseHex(dbl, out C4b a)); + Assert.IsTrue(C4b.TryParse(sgl, out C4b b)); + Assert.AreEqual(a, b); + } + } } } From 68c687fad31bf273d4331e6ef26f6eecc07817c2 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 19 Oct 2023 18:13:27 +0200 Subject: [PATCH 13/45] Update RELEASE_NOTES.md --- RELEASE_NOTES.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 2cb2c692..ff75f248 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,20 @@ +### 5.2.27 +* Improved and fixed RangeSet implementation +* Added utilities for ValueOption +* Added more ArraySegment utilities +* [Text] Fixed NestedBracketSplitCount +* [Color] Fixed overflow issue in C4ui constructors +* [Color] Improved color parsing, now supports hexadecimal color strings +* [PixImage] Fixed file handle leak in Windows Media loader +* [MapExt] Added value variants of some operations + ### 5.2.26 * LinearRegression3d: made fields public +* Added getLines, normalizeLineEndings, withLineNumbers in String module +* Fixed FGetValueOrDefault dictionary extension +* Added value variants for Dict functions +* Added F# ArraySegment utilities +* [Introspection] Tidied up exception reporting ### 5.2.25 * Fixed issue with PixImageMipMap loading From 330c27d9411bb23611860d64a02de80cf9bb179b Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 12:40:15 +0100 Subject: [PATCH 14/45] Reorganize Aardvark.Base.FSharp project --- .../Aardvark.Base.FSharp.fsproj | 51 ++++++++++--------- .../{Prelude => Math}/AverageWindow.fs | 0 .../{Interop => Math}/Converters.fs | 0 .../{Prelude => Math}/Math.fs | 0 .../{Interop => Math}/Matrix.fs | 0 .../{ => Math}/SVDM33f.fs | 0 .../{Interop => Math}/Vectors.fs | 0 .../{ => Utilities}/Interop/ArraySegment.fs | 0 .../{ => Utilities}/Interop/CSharpList.fs | 0 .../{ => Utilities}/Interop/Dictionary.fs | 0 .../Interop}/FSLibExtensions.fs | 0 .../{ => Utilities}/Interop/HashSet.fs | 0 .../{ => Utilities}/Interop/SortedSet.fs | 0 .../{ => Utilities/Interop}/String.fs | 0 .../Interop/Strings.fs} | 0 .../{ => Utilities}/Interop/Symbol.fs | 0 .../{Prelude => Utilities}/Lens.fs | 0 .../{Prelude => Utilities}/Logging.fs | 0 .../{Prelude => Utilities}/Measures.fs | 4 +- .../{Control.fs => Utilities/Monads.fs} | 0 .../{Prelude => Utilities}/Monoid.fs | 0 .../Pickler}/AdaptivePicklers.fs | 0 .../Pickler}/FsPicklerExtensions.fs | 0 23 files changed, 28 insertions(+), 27 deletions(-) rename src/Aardvark.Base.FSharp/{Prelude => Math}/AverageWindow.fs (100%) rename src/Aardvark.Base.FSharp/{Interop => Math}/Converters.fs (100%) rename src/Aardvark.Base.FSharp/{Prelude => Math}/Math.fs (100%) rename src/Aardvark.Base.FSharp/{Interop => Math}/Matrix.fs (100%) rename src/Aardvark.Base.FSharp/{ => Math}/SVDM33f.fs (100%) rename src/Aardvark.Base.FSharp/{Interop => Math}/Vectors.fs (100%) rename src/Aardvark.Base.FSharp/{ => Utilities}/Interop/ArraySegment.fs (100%) rename src/Aardvark.Base.FSharp/{ => Utilities}/Interop/CSharpList.fs (100%) rename src/Aardvark.Base.FSharp/{ => Utilities}/Interop/Dictionary.fs (100%) rename src/Aardvark.Base.FSharp/{Prelude => Utilities/Interop}/FSLibExtensions.fs (100%) rename src/Aardvark.Base.FSharp/{ => Utilities}/Interop/HashSet.fs (100%) rename src/Aardvark.Base.FSharp/{ => Utilities}/Interop/SortedSet.fs (100%) rename src/Aardvark.Base.FSharp/{ => Utilities/Interop}/String.fs (100%) rename src/Aardvark.Base.FSharp/{Interop/String.fs => Utilities/Interop/Strings.fs} (100%) rename src/Aardvark.Base.FSharp/{ => Utilities}/Interop/Symbol.fs (100%) rename src/Aardvark.Base.FSharp/{Prelude => Utilities}/Lens.fs (100%) rename src/Aardvark.Base.FSharp/{Prelude => Utilities}/Logging.fs (100%) rename src/Aardvark.Base.FSharp/{Prelude => Utilities}/Measures.fs (99%) rename src/Aardvark.Base.FSharp/{Control.fs => Utilities/Monads.fs} (100%) rename src/Aardvark.Base.FSharp/{Prelude => Utilities}/Monoid.fs (100%) rename src/Aardvark.Base.FSharp/{ => Utilities/Pickler}/AdaptivePicklers.fs (100%) rename src/Aardvark.Base.FSharp/{ => Utilities/Pickler}/FsPicklerExtensions.fs (100%) diff --git a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj index 0bbf51df..ba54fa62 100644 --- a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj +++ b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj @@ -15,23 +15,28 @@ ..\..\bin\Release - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + @@ -69,23 +74,19 @@ - - - - - - - - + + + + \ No newline at end of file diff --git a/src/Aardvark.Base.FSharp/Prelude/AverageWindow.fs b/src/Aardvark.Base.FSharp/Math/AverageWindow.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Prelude/AverageWindow.fs rename to src/Aardvark.Base.FSharp/Math/AverageWindow.fs diff --git a/src/Aardvark.Base.FSharp/Interop/Converters.fs b/src/Aardvark.Base.FSharp/Math/Converters.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Interop/Converters.fs rename to src/Aardvark.Base.FSharp/Math/Converters.fs diff --git a/src/Aardvark.Base.FSharp/Prelude/Math.fs b/src/Aardvark.Base.FSharp/Math/Math.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Prelude/Math.fs rename to src/Aardvark.Base.FSharp/Math/Math.fs diff --git a/src/Aardvark.Base.FSharp/Interop/Matrix.fs b/src/Aardvark.Base.FSharp/Math/Matrix.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Interop/Matrix.fs rename to src/Aardvark.Base.FSharp/Math/Matrix.fs diff --git a/src/Aardvark.Base.FSharp/SVDM33f.fs b/src/Aardvark.Base.FSharp/Math/SVDM33f.fs similarity index 100% rename from src/Aardvark.Base.FSharp/SVDM33f.fs rename to src/Aardvark.Base.FSharp/Math/SVDM33f.fs diff --git a/src/Aardvark.Base.FSharp/Interop/Vectors.fs b/src/Aardvark.Base.FSharp/Math/Vectors.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Interop/Vectors.fs rename to src/Aardvark.Base.FSharp/Math/Vectors.fs diff --git a/src/Aardvark.Base.FSharp/Interop/ArraySegment.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/ArraySegment.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Interop/ArraySegment.fs rename to src/Aardvark.Base.FSharp/Utilities/Interop/ArraySegment.fs diff --git a/src/Aardvark.Base.FSharp/Interop/CSharpList.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/CSharpList.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Interop/CSharpList.fs rename to src/Aardvark.Base.FSharp/Utilities/Interop/CSharpList.fs diff --git a/src/Aardvark.Base.FSharp/Interop/Dictionary.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Interop/Dictionary.fs rename to src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs diff --git a/src/Aardvark.Base.FSharp/Prelude/FSLibExtensions.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Prelude/FSLibExtensions.fs rename to src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs diff --git a/src/Aardvark.Base.FSharp/Interop/HashSet.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/HashSet.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Interop/HashSet.fs rename to src/Aardvark.Base.FSharp/Utilities/Interop/HashSet.fs diff --git a/src/Aardvark.Base.FSharp/Interop/SortedSet.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/SortedSet.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Interop/SortedSet.fs rename to src/Aardvark.Base.FSharp/Utilities/Interop/SortedSet.fs diff --git a/src/Aardvark.Base.FSharp/String.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/String.fs similarity index 100% rename from src/Aardvark.Base.FSharp/String.fs rename to src/Aardvark.Base.FSharp/Utilities/Interop/String.fs diff --git a/src/Aardvark.Base.FSharp/Interop/String.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/Strings.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Interop/String.fs rename to src/Aardvark.Base.FSharp/Utilities/Interop/Strings.fs diff --git a/src/Aardvark.Base.FSharp/Interop/Symbol.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/Symbol.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Interop/Symbol.fs rename to src/Aardvark.Base.FSharp/Utilities/Interop/Symbol.fs diff --git a/src/Aardvark.Base.FSharp/Prelude/Lens.fs b/src/Aardvark.Base.FSharp/Utilities/Lens.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Prelude/Lens.fs rename to src/Aardvark.Base.FSharp/Utilities/Lens.fs diff --git a/src/Aardvark.Base.FSharp/Prelude/Logging.fs b/src/Aardvark.Base.FSharp/Utilities/Logging.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Prelude/Logging.fs rename to src/Aardvark.Base.FSharp/Utilities/Logging.fs diff --git a/src/Aardvark.Base.FSharp/Prelude/Measures.fs b/src/Aardvark.Base.FSharp/Utilities/Measures.fs similarity index 99% rename from src/Aardvark.Base.FSharp/Prelude/Measures.fs rename to src/Aardvark.Base.FSharp/Utilities/Measures.fs index bd3fe7c9..279c524b 100644 --- a/src/Aardvark.Base.FSharp/Prelude/Measures.fs +++ b/src/Aardvark.Base.FSharp/Utilities/Measures.fs @@ -135,8 +135,8 @@ type MicroTime = new (ts : TimeSpan) = { TotalNanoseconds = ts.Ticks * (1000000000L / TimeSpan.TicksPerSecond) } new (ns : float) = { TotalNanoseconds = - if isFinite ns then int64 ns - elif isPositiveInfinity ns then Int64.MaxValue + if Fun.IsFinite ns then int64 ns + elif Fun.IsPositiveInfinity ns then Int64.MaxValue else Int64.MinValue } diff --git a/src/Aardvark.Base.FSharp/Control.fs b/src/Aardvark.Base.FSharp/Utilities/Monads.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Control.fs rename to src/Aardvark.Base.FSharp/Utilities/Monads.fs diff --git a/src/Aardvark.Base.FSharp/Prelude/Monoid.fs b/src/Aardvark.Base.FSharp/Utilities/Monoid.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Prelude/Monoid.fs rename to src/Aardvark.Base.FSharp/Utilities/Monoid.fs diff --git a/src/Aardvark.Base.FSharp/AdaptivePicklers.fs b/src/Aardvark.Base.FSharp/Utilities/Pickler/AdaptivePicklers.fs similarity index 100% rename from src/Aardvark.Base.FSharp/AdaptivePicklers.fs rename to src/Aardvark.Base.FSharp/Utilities/Pickler/AdaptivePicklers.fs diff --git a/src/Aardvark.Base.FSharp/FsPicklerExtensions.fs b/src/Aardvark.Base.FSharp/Utilities/Pickler/FsPicklerExtensions.fs similarity index 100% rename from src/Aardvark.Base.FSharp/FsPicklerExtensions.fs rename to src/Aardvark.Base.FSharp/Utilities/Pickler/FsPicklerExtensions.fs From 8a5a2e5f2c4f17e2e3903869eb643da9d9328e01 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 12:44:35 +0100 Subject: [PATCH 15/45] [FSharp] Remove Strings.fs --- .../Aardvark.Base.FSharp.fsproj | 1 - .../Utilities/Interop/String.fs | 16 ++++++++++++++ .../Utilities/Interop/Strings.fs | 22 ------------------- 3 files changed, 16 insertions(+), 23 deletions(-) delete mode 100644 src/Aardvark.Base.FSharp/Utilities/Interop/Strings.fs diff --git a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj index ba54fa62..9b0498ad 100644 --- a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj +++ b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj @@ -20,7 +20,6 @@ - diff --git a/src/Aardvark.Base.FSharp/Utilities/Interop/String.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/String.fs index 5f3d6aaf..6e7b1413 100644 --- a/src/Aardvark.Base.FSharp/Utilities/Interop/String.fs +++ b/src/Aardvark.Base.FSharp/Utilities/Interop/String.fs @@ -9,6 +9,22 @@ open System open System.Text.RegularExpressions open System.Runtime.CompilerServices +[] +[] +module Strings = + let partRx = Regex @"([A-Z][a-z0-9]*)[_]*" + + /// checks whether pattern is contained in str + let contains pattern (str : string) = str.Contains pattern + + let toLower (str : string) = str.ToLower() + let toUpper (str : string) = str.ToUpper() + + let inline split (sep : string) (str : string) = str.Split([| sep |], StringSplitOptions.None) + let inline startsWith (s : string) (str : string) = str.StartsWith s + let inline endsWith (s : string) (str : string) = str.EndsWith s + let inline trim (str : string) = str.Trim() + [] type StringExtensions private() = diff --git a/src/Aardvark.Base.FSharp/Utilities/Interop/Strings.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/Strings.fs deleted file mode 100644 index 40353c6d..00000000 --- a/src/Aardvark.Base.FSharp/Utilities/Interop/Strings.fs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Aardvark.Base - -open System - -[] -[] -module Strings = - let partRx = System.Text.RegularExpressions.Regex @"([A-Z][a-z0-9]*)[_]*" - - /// checks whether pattern is contained in str - let contains pattern (str : string) = str.Contains pattern - - let toLower (str : string) = str.ToLower() - let toUpper (str : string) = str.ToUpper() - - let inline split (sep : string) (str : string) = str.Split([| sep |], StringSplitOptions.None) - let inline startsWith (s : string) (str : string) = str.StartsWith s - let inline endsWith (s : string) (str : string) = str.EndsWith s - let inline trim (str : string) = str.Trim() - - - From ef7bb58bac6d94b34eb832895d99af4b54414b35 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 12:58:17 +0100 Subject: [PATCH 16/45] [FSharp] Move ConversionHelpers --- src/Aardvark.Base.FSharp/Runtime.fs | 35 +------------------ .../Utilities/Interop/FSLibExtensions.fs | 16 +++++++++ 2 files changed, 17 insertions(+), 34 deletions(-) diff --git a/src/Aardvark.Base.FSharp/Runtime.fs b/src/Aardvark.Base.FSharp/Runtime.fs index 12388cb5..027bba72 100644 --- a/src/Aardvark.Base.FSharp/Runtime.fs +++ b/src/Aardvark.Base.FSharp/Runtime.fs @@ -456,37 +456,4 @@ module MarshalDelegateExtensions = Marshal.PinDelegate(Func<'a, 'b, 'c>(f)) static member PinFunction(f : 'a -> 'b -> 'c -> 'd) = - Marshal.PinDelegate(Func<'a, 'b, 'c, 'd>(f)) - - -module ConversionHelpers = - open System.Collections.Generic - - let lookupTableOption (l : list<'a * 'b>) = - let d = Dictionary() - for (k,v) in l do - - match d.TryGetValue k with - | (true, vo) -> failwithf "duplicated lookup-entry: %A (%A vs %A)" k vo v - | _ -> () - - d.[k] <- v - - fun (key : 'a) -> - match d.TryGetValue key with - | (true, v) -> Some v - | _ -> None - - let lookupTable (l : list<'a * 'b>) = - let tryLookup = lookupTableOption l - fun (key : 'a) -> - match tryLookup key with - | Some v -> v - | _ -> failwithf "unsupported %A: %A" typeof<'a> key - - let inline convertEnum< ^a, ^b when ^a : (static member op_Explicit : ^a -> int)> (fmt : ^a) : ^b = - let v = int fmt - if Enum.IsDefined(typeof< ^b >, v) then - unbox< ^b > v - else - failwithf "cannot convert %s %A to %s" typeof< ^a >.Name fmt typeof< ^b >.Name \ No newline at end of file + Marshal.PinDelegate(Func<'a, 'b, 'c, 'd>(f)) \ No newline at end of file diff --git a/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs index 1d7fe00a..f9e56d60 100644 --- a/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs +++ b/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs @@ -858,5 +858,21 @@ module NativeUtilities = finally gc.Free() +module ConversionHelpers = + + [] + let lookupTableOption (l : list<'a * 'b>) : ('a -> 'b option) = + LookupTable.lookupTable' l + + [] + let lookupTable (l : list<'a * 'b>) : ('a -> 'b) = + LookupTable.lookupTable l + + let inline convertEnum< ^a, ^b when ^a : (static member op_Explicit : ^a -> int)> (fmt : ^a) : ^b = + let v = int fmt + if Enum.IsDefined(typeof< ^b >, v) then + unbox< ^b > v + else + failwithf "cannot convert %s %A to %s" typeof< ^a >.Name fmt typeof< ^b >.Name type float16 = Aardvark.Base.Half \ No newline at end of file From 0e3c21a5002efe7d96ab6976435ddbfc13bf76c3 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 13:00:29 +0100 Subject: [PATCH 17/45] [FSharp] Move MarshalDelegateExtensions --- src/Aardvark.Base.FSharp/Runtime.fs | 29 +------------------ .../Utilities/Interop/FSLibExtensions.fs | 27 +++++++++++++++++ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/Aardvark.Base.FSharp/Runtime.fs b/src/Aardvark.Base.FSharp/Runtime.fs index 027bba72..31288543 100644 --- a/src/Aardvark.Base.FSharp/Runtime.fs +++ b/src/Aardvark.Base.FSharp/Runtime.fs @@ -429,31 +429,4 @@ module ReflectionPatterns = let (|Create|_|) (c : ConstructorInfo) = - Create(c.DeclaringType, c.GetParameters() |> Seq.toList) |> Some - -[] -module MarshalDelegateExtensions = - open System.Runtime.InteropServices - open System.Collections.Concurrent - - let private pinnedDelegates = ConcurrentDictionary() - type PinnedDelegate internal(d : Delegate, ptr : nativeint) = - member x.Pointer = ptr - member x.Dispose() = pinnedDelegates.TryRemove d |> ignore - - interface IDisposable with - member x.Dispose() = x.Dispose() - - type Marshal with - static member PinDelegate(d : Delegate) = - let ptr = pinnedDelegates.GetOrAdd(d, fun _ -> Marshal.GetFunctionPointerForDelegate d) - new PinnedDelegate(d, ptr) - - static member PinFunction(f : 'a -> 'b) = - Marshal.PinDelegate(Func<'a, 'b>(f)) - - static member PinFunction(f : 'a -> 'b -> 'c) = - Marshal.PinDelegate(Func<'a, 'b, 'c>(f)) - - static member PinFunction(f : 'a -> 'b -> 'c -> 'd) = - Marshal.PinDelegate(Func<'a, 'b, 'c, 'd>(f)) \ No newline at end of file + Create(c.DeclaringType, c.GetParameters() |> Seq.toList) |> Some \ No newline at end of file diff --git a/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs index f9e56d60..7c15d515 100644 --- a/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs +++ b/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs @@ -858,6 +858,33 @@ module NativeUtilities = finally gc.Free() +[] +module MarshalDelegateExtensions = + open System.Runtime.InteropServices + open System.Collections.Concurrent + + let private pinnedDelegates = ConcurrentDictionary() + type PinnedDelegate internal(d : Delegate, ptr : nativeint) = + member x.Pointer = ptr + member x.Dispose() = pinnedDelegates.TryRemove d |> ignore + + interface IDisposable with + member x.Dispose() = x.Dispose() + + type Marshal with + static member PinDelegate(d : Delegate) = + let ptr = pinnedDelegates.GetOrAdd(d, fun _ -> Marshal.GetFunctionPointerForDelegate d) + new PinnedDelegate(d, ptr) + + static member PinFunction(f : 'a -> 'b) = + Marshal.PinDelegate(Func<'a, 'b>(f)) + + static member PinFunction(f : 'a -> 'b -> 'c) = + Marshal.PinDelegate(Func<'a, 'b, 'c>(f)) + + static member PinFunction(f : 'a -> 'b -> 'c -> 'd) = + Marshal.PinDelegate(Func<'a, 'b, 'c, 'd>(f)) + module ConversionHelpers = [] From a88f0d2020c4989a931338d52f6d012c936d6087 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 13:08:19 +0100 Subject: [PATCH 18/45] [FSharp] Move ReflectionHelpers and ReflectionPatterns --- .../Reflection/ReflectionExtensions.fs | 150 ++++++++++++++++- src/Aardvark.Base.FSharp/Runtime.fs | 151 +----------------- 2 files changed, 150 insertions(+), 151 deletions(-) diff --git a/src/Aardvark.Base.FSharp/Reflection/ReflectionExtensions.fs b/src/Aardvark.Base.FSharp/Reflection/ReflectionExtensions.fs index 10267f5a..aa94982b 100644 --- a/src/Aardvark.Base.FSharp/Reflection/ReflectionExtensions.fs +++ b/src/Aardvark.Base.FSharp/Reflection/ReflectionExtensions.fs @@ -336,5 +336,153 @@ type MethodInfoGenericExtensions private() = match MethodResolver.tryMakeApplicable args ret this with | Some m -> m | None -> null - + +[] +module ReflectionHelpers = + open Microsoft.FSharp.Reflection + + let private lockObj = obj() + + let private prettyNames = + Dict.ofList [ + typeof, "sbyte" + typeof, "byte" + typeof, "int16" + typeof, "uint16" + typeof, "int" + typeof, "uint32" + typeof, "int64" + typeof, "uint64" + typeof, "nativeint" + typeof, "unativeint" + + typeof, "char" + typeof, "string" + + + typeof, "float32" + typeof, "float" + typeof, "decimal" + + typeof, "obj" + typeof, "unit" + typeof, "void" + + ] + + let private genericPrettyNames = + Dict.ofList [ + typedefof>, "list" + typedefof>, "Option" + typedefof>, "Set" + typedefof>, "Map" + typedefof>, "seq" + + ] + + let private idRx = System.Text.RegularExpressions.Regex @"[a-zA-Z_][a-zA-Z_0-9]*" + + let rec private getPrettyNameInternal (t : Type) = + let res = + match prettyNames.TryGetValue t with + | (true, n) -> n + | _ -> + if t.IsArray then + t.GetElementType() |> getPrettyNameInternal |> sprintf "%s[]" + + elif FSharpType.IsTuple t then + FSharpType.GetTupleElements t |> Seq.map getPrettyNameInternal |> String.concat " * " + + elif FSharpType.IsFunction t then + let (arg, res) = FSharpType.GetFunctionElements t + + sprintf "%s -> %s" (getPrettyNameInternal arg) (getPrettyNameInternal res) + + elif typeof.IsAssignableFrom t then + let s = Aardvark.Base.Peano.getSize t + sprintf "N%d" s + + elif t.IsGenericType then + let args = t.GetGenericArguments() |> Seq.map getPrettyNameInternal |> String.concat ", " + let bt = t.GetGenericTypeDefinition() + match genericPrettyNames.TryGetValue bt with + | (true, gen) -> + sprintf "%s<%s>" gen args + | _ -> + let gen = idRx.Match bt.Name + sprintf "%s<%s>" gen.Value args + + + else + t.Name + + prettyNames.[t] <- res + res + + [] + let getPrettyName(t : Type) = + lock lockObj (fun () -> + getPrettyNameInternal t + ) + + type Type with + member x.PrettyName = + lock lockObj (fun () -> + getPrettyNameInternal x + ) + + +/// +/// Defines a number of active patterns for matching expressions. Includes some +/// functionality missing in F#. +/// +[] +module ReflectionPatterns = + open Microsoft.FSharp.Quotations + open QuotationReflectionHelpers + + let private typePrefixPattern = System.Text.RegularExpressions.Regex @"^.*\.(?.*)$" + let (|Method|_|) (mi : MethodInfo) = + let args = mi.GetParameters() |> Seq.map(fun p -> p.ParameterType) + let parameters = if mi.IsStatic then + args + else + seq { yield mi.DeclaringType; yield! args } + + let m = typePrefixPattern.Match mi.Name + let name = + if m.Success then m.Groups.["methodName"].Value + else mi.Name + + Method (name, parameters |> Seq.toList) |> Some + + let private compareMethods (template : MethodInfo) (m : MethodInfo) = + if template.IsGenericMethod && m.IsGenericMethod then + if template.GetGenericMethodDefinition() = m.GetGenericMethodDefinition() then + let targs = template.GetGenericArguments() |> Array.toList + let margs = m.GetGenericArguments() |> Array.toList + + let zip = List.zip targs margs + + let args = zip |> List.filter(fun (l,r) -> l.IsGenericParameter) |> List.map (fun (_,a) -> a) + + Some args + else + None + elif template = m then + Some [] + else + None + + let (|MethodQuote|_|) (e : Expr) (mi : MethodInfo) = + let m = tryGetMethodInfo e + match m with + | Some m -> match compareMethods m mi with + | Some a -> MethodQuote(a) |> Some + | None -> None + | _ -> None + + + let (|Create|_|) (c : ConstructorInfo) = + Create(c.DeclaringType, c.GetParameters() |> Seq.toList) |> Some \ No newline at end of file diff --git a/src/Aardvark.Base.FSharp/Runtime.fs b/src/Aardvark.Base.FSharp/Runtime.fs index 31288543..7703c660 100644 --- a/src/Aardvark.Base.FSharp/Runtime.fs +++ b/src/Aardvark.Base.FSharp/Runtime.fs @@ -280,153 +280,4 @@ module Weak = false else List.fold2 (fun b l r -> b && (l.Equals(r))) true m_elements other.Target - | _ -> false - -[] -module ReflectionHelpers = - open Microsoft.FSharp.Reflection - - let private lockObj = obj() - - let private prettyNames = - Dict.ofList [ - typeof, "sbyte" - typeof, "byte" - typeof, "int16" - typeof, "uint16" - typeof, "int" - typeof, "uint32" - typeof, "int64" - typeof, "uint64" - typeof, "nativeint" - typeof, "unativeint" - - typeof, "char" - typeof, "string" - - - typeof, "float32" - typeof, "float" - typeof, "decimal" - - typeof, "obj" - typeof, "unit" - typeof, "void" - - ] - - let private genericPrettyNames = - Dict.ofList [ - typedefof>, "list" - typedefof>, "Option" - typedefof>, "Set" - typedefof>, "Map" - typedefof>, "seq" - - ] - - let private idRx = System.Text.RegularExpressions.Regex @"[a-zA-Z_][a-zA-Z_0-9]*" - - let rec private getPrettyNameInternal (t : Type) = - let res = - match prettyNames.TryGetValue t with - | (true, n) -> n - | _ -> - if t.IsArray then - t.GetElementType() |> getPrettyNameInternal |> sprintf "%s[]" - - elif FSharpType.IsTuple t then - FSharpType.GetTupleElements t |> Seq.map getPrettyNameInternal |> String.concat " * " - - elif FSharpType.IsFunction t then - let (arg, res) = FSharpType.GetFunctionElements t - - sprintf "%s -> %s" (getPrettyNameInternal arg) (getPrettyNameInternal res) - - elif typeof.IsAssignableFrom t then - let s = Aardvark.Base.Peano.getSize t - sprintf "N%d" s - - elif t.IsGenericType then - let args = t.GetGenericArguments() |> Seq.map getPrettyNameInternal |> String.concat ", " - let bt = t.GetGenericTypeDefinition() - match genericPrettyNames.TryGetValue bt with - | (true, gen) -> - sprintf "%s<%s>" gen args - | _ -> - let gen = idRx.Match bt.Name - sprintf "%s<%s>" gen.Value args - - - else - t.Name - - prettyNames.[t] <- res - res - - [] - let getPrettyName(t : Type) = - lock lockObj (fun () -> - getPrettyNameInternal t - ) - - type Type with - member x.PrettyName = - lock lockObj (fun () -> - getPrettyNameInternal x - ) - -/// -/// Defines a number of active patterns for matching expressions. Includes some -/// functionality missing in F#. -/// -[] -module ReflectionPatterns = - open System.Reflection - open Microsoft.FSharp.Quotations - open QuotationReflectionHelpers - - let private typePrefixPattern = System.Text.RegularExpressions.Regex @"^.*\.(?.*)$" - let (|Method|_|) (mi : MethodInfo) = - let args = mi.GetParameters() |> Seq.map(fun p -> p.ParameterType) - let parameters = if mi.IsStatic then - args - else - seq { yield mi.DeclaringType; yield! args } - - let m = typePrefixPattern.Match mi.Name - let name = - if m.Success then m.Groups.["methodName"].Value - else mi.Name - - Method (name, parameters |> Seq.toList) |> Some - - let private compareMethods (template : MethodInfo) (m : MethodInfo) = - if template.IsGenericMethod && m.IsGenericMethod then - if template.GetGenericMethodDefinition() = m.GetGenericMethodDefinition() then - let targs = template.GetGenericArguments() |> Array.toList - let margs = m.GetGenericArguments() |> Array.toList - - let zip = List.zip targs margs - - let args = zip |> List.filter(fun (l,r) -> l.IsGenericParameter) |> List.map (fun (_,a) -> a) - - Some args - else - None - elif template = m then - Some [] - else - None - - let (|MethodQuote|_|) (e : Expr) (mi : MethodInfo) = - let m = tryGetMethodInfo e - match m with - | Some m -> match compareMethods m mi with - | Some a -> MethodQuote(a) |> Some - | None -> None - | _ -> None - - - let (|Create|_|) (c : ConstructorInfo) = - Create(c.DeclaringType, c.GetParameters() |> Seq.toList) |> Some \ No newline at end of file + | _ -> false \ No newline at end of file From 7d4b6eaae6cfd003287560983bcfce17e8dab683 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 13:10:17 +0100 Subject: [PATCH 19/45] [FSharp] Rename Runtime.fs to Weak.fs --- src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj | 2 +- src/Aardvark.Base.FSharp/{Runtime.fs => Utilities/Weak.fs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/Aardvark.Base.FSharp/{Runtime.fs => Utilities/Weak.fs} (100%) diff --git a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj index 9b0498ad..27a43e15 100644 --- a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj +++ b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj @@ -30,6 +30,7 @@ + @@ -79,7 +80,6 @@ - diff --git a/src/Aardvark.Base.FSharp/Runtime.fs b/src/Aardvark.Base.FSharp/Utilities/Weak.fs similarity index 100% rename from src/Aardvark.Base.FSharp/Runtime.fs rename to src/Aardvark.Base.FSharp/Utilities/Weak.fs From bddcbc5ef3aaf4e4dd1601504289423717b1fc66 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 14:06:01 +0100 Subject: [PATCH 20/45] Add IDictionary.TryPop overload with out parameter --- .../Utilities/Interop/Dictionary.fs | 26 +++++++++++++++++++ .../Utilities/Interop/FSLibExtensions.fs | 24 ----------------- .../Extensions/DictionaryExtensions.cs | 19 +++++++++----- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs index 4f12f256..de3340c4 100644 --- a/src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs +++ b/src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs @@ -1,5 +1,31 @@ namespace Aardvark.Base +[] +module CSharpCollectionExtensions = + open System + open System.Runtime.CompilerServices + open System.Collections.Generic + open System.Runtime.InteropServices + + type public DictionaryExtensions = + + [] + [] + static member TryRemove(x : Dictionary<'a,'b>, k,[] r: byref<'b>) = + match x.TryGetValue k with + | (true,v) -> r <- v; true + | _ -> false + + [] + [] + static member GetOrAdd(x : Dictionary<'a,'b>, k : 'a, creator : 'a -> 'b) = + match x.TryGetValue k with + | (true,v) -> v + | _ -> + let v = creator k + x.Add(k,v) |> ignore + v + [] module Dictionary = open System.Collections.Generic diff --git a/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs index 7c15d515..69556605 100644 --- a/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs +++ b/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs @@ -337,30 +337,6 @@ module CSharpInterop = static member Create<'a,'b,'c,'d> (func:System.Func<'a,'b,'c,'d>) = FSharpFuncUtil.ToFSharpFunc func -[] -module CSharpCollectionExtensions = - - open System.Runtime.CompilerServices - open System.Collections.Generic - open System.Runtime.InteropServices - - type public DictionaryExtensions = - - [] - static member TryRemove(x : Dictionary<'a,'b>, k,[] r: byref<'b>) = - match x.TryGetValue k with - | (true,v) -> r <- v; true - | _ -> false - - [] - static member GetOrAdd(x : Dictionary<'a,'b>, k : 'a, creator : 'a -> 'b) = - match x.TryGetValue k with - | (true,v) -> v - | _ -> - let v = creator k - x.Add(k,v) |> ignore - v - [] module Threading = open System.Threading diff --git a/src/Aardvark.Base/Extensions/DictionaryExtensions.cs b/src/Aardvark.Base/Extensions/DictionaryExtensions.cs index c53cce0d..7e4aff4e 100644 --- a/src/Aardvark.Base/Extensions/DictionaryExtensions.cs +++ b/src/Aardvark.Base/Extensions/DictionaryExtensions.cs @@ -116,18 +116,23 @@ public static TV Pop(this IDictionary self, TK key) throw new KeyNotFoundException(); } + /// + /// Removes the value with the given key from the dictionary and passes it as out argument. + /// In case the value is not found default(T) will be returned. + /// + /// true if the value was removed from the dictionary, false otherwise. + public static bool TryPop(this IDictionary self, TK key, out TV value) + => self.TryGetValue(key, out value) && self.Remove(key); + /// /// Removes the value with the given key from the dictionary and passes it as return argument. - /// In case the value is not found default(T) will be return. + /// In case the value is not found default(T) will be returned. /// /// The value removed from the dictionary or default(T) public static TV TryPop(this IDictionary self, TK key) - { - if (self.TryGetValue(key, out TV value)) - self.Remove(key); - return value; - } - + => self.TryPop(key, out TV value) ? value : default; + + public static T1v[] CopyToArray(this IDictionary self, Func, T1v> fun) { var r = new T1v[self.Count]; From 9bfea85413354a73ef4bd42a51133c56e7fcd67f Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 14:12:31 +0100 Subject: [PATCH 21/45] [FSharp] Move threading utlities to separate file --- .../Aardvark.Base.FSharp.fsproj | 1 + .../Utilities/Interop/FSLibExtensions.fs | 137 +---------------- .../Utilities/Threading.fs | 140 ++++++++++++++++++ 3 files changed, 142 insertions(+), 136 deletions(-) create mode 100644 src/Aardvark.Base.FSharp/Utilities/Threading.fs diff --git a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj index 27a43e15..9e97d856 100644 --- a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj +++ b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj @@ -26,6 +26,7 @@ + diff --git a/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs index 69556605..6e1f82f1 100644 --- a/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs +++ b/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs @@ -1,7 +1,7 @@ namespace Aardvark.Base + #nowarn "9" #nowarn "51" -#nowarn "44" open System open FSharp.NativeInterop @@ -337,141 +337,6 @@ module CSharpInterop = static member Create<'a,'b,'c,'d> (func:System.Func<'a,'b,'c,'d>) = FSharpFuncUtil.ToFSharpFunc func -[] -module Threading = - open System.Threading - - /// Please note that Aardvark.Base.FSharp's MVar implementation is different from Haskell's MVar introduced in - /// "Concurrent Haskell" by Simon Peyton Jones, Andrew Gordon and Sigbjorn Finne. - /// see also: http://hackage.haskell.org/package/base-4.11.1.0/docs/Control-Concurrent-MVar.html - /// In our 'wrong' implementation put does not block but overrides the old value. - /// We use it typically for synchronized sampling use cases. - type MVar<'a>() = - let l = obj() - - let mutable hasValue = false - let mutable content = Unchecked.defaultof<'a> - - member x.Put v = - lock l (fun () -> - content <- v - if not hasValue then - hasValue <- true - Monitor.PulseAll l - ) - - member x.Take () = - lock l (fun () -> - while not hasValue do - Monitor.Wait l |> ignore - let v = content - content <- Unchecked.defaultof<_> - hasValue <- false - v - ) - - [] - member x.TakeAsync () = - async { - let! ct = Async.CancellationToken - do! Async.SwitchToThreadPool() - return x.Take() - } - - - let startThread (f : unit -> unit) = - let t = new Thread(ThreadStart f) - t.IsBackground <- true - t.Start() - t - - - [] - module MVar = - let empty () = MVar<'a>() - let create a = - let v = empty() - v.Put a - v - let put (m : MVar<'a>) v = m.Put v - let take (m : MVar<'a>) = m.Take() - [] - let takeAsync (m : MVar<'a>) = m.TakeAsync () - - type Interlocked with - static member Change(location : byref<'a>, f : 'a -> 'a) = - let mutable initial = location - let mutable computed = f initial - - while Interlocked.CompareExchange(&location, computed, initial) != initial do - initial <- location - computed <- f initial - - computed - - static member Change(location : byref<'a>, f : 'a -> 'a * 'b) = - let mutable initial = location - let (n,r) = f initial - let mutable computed = n - let mutable result = r - - while Interlocked.CompareExchange(&location, computed, initial) != initial do - initial <- location - let (n,r) = f initial - computed <- n - result <- r - - result - - - static member Change(location : byref, f : int -> int) = - let mutable initial = location - let mutable computed = f initial - - while Interlocked.CompareExchange(&location, computed, initial) <> initial do - initial <- location - computed <- f initial - - computed - - static member Change(location : byref, f : int -> int * 'b) = - let mutable initial = location - let (n,r) = f initial - let mutable computed = n - let mutable result = r - - while Interlocked.CompareExchange(&location, computed, initial) <> initial do - initial <- location - let (n,r) = f initial - computed <- n - result <- r - - result - - static member Change(location : byref, f : int64 -> int64) = - let mutable initial = location - let mutable computed = f initial - - while Interlocked.CompareExchange(&location, computed, initial) <> initial do - initial <- location - computed <- f initial - - computed - - static member Change(location : byref, f : int64 -> int64 * 'b) = - let mutable initial = location - let (n,r) = f initial - let mutable computed = n - let mutable result = r - - while Interlocked.CompareExchange(&location, computed, initial) <> initial do - initial <- location - let (n,r) = f initial - computed <- n - result <- r - - result - module GenericValues = open System.Reflection diff --git a/src/Aardvark.Base.FSharp/Utilities/Threading.fs b/src/Aardvark.Base.FSharp/Utilities/Threading.fs new file mode 100644 index 00000000..7a5ebf6a --- /dev/null +++ b/src/Aardvark.Base.FSharp/Utilities/Threading.fs @@ -0,0 +1,140 @@ +namespace Aardvark.Base + +#nowarn "44" + +open System + +[] +module Threading = + open System.Threading + + /// Please note that Aardvark.Base.FSharp's MVar implementation is different from Haskell's MVar introduced in + /// "Concurrent Haskell" by Simon Peyton Jones, Andrew Gordon and Sigbjorn Finne. + /// see also: http://hackage.haskell.org/package/base-4.11.1.0/docs/Control-Concurrent-MVar.html + /// In our 'wrong' implementation put does not block but overrides the old value. + /// We use it typically for synchronized sampling use cases. + type MVar<'a>() = + let l = obj() + + let mutable hasValue = false + let mutable content = Unchecked.defaultof<'a> + + member x.Put v = + lock l (fun () -> + content <- v + if not hasValue then + hasValue <- true + Monitor.PulseAll l + ) + + member x.Take () = + lock l (fun () -> + while not hasValue do + Monitor.Wait l |> ignore + let v = content + content <- Unchecked.defaultof<_> + hasValue <- false + v + ) + + [] + member x.TakeAsync () = + async { + let! ct = Async.CancellationToken + do! Async.SwitchToThreadPool() + return x.Take() + } + + + let startThread (f : unit -> unit) = + let t = new Thread(ThreadStart f) + t.IsBackground <- true + t.Start() + t + + + [] + module MVar = + let empty () = MVar<'a>() + let create a = + let v = empty() + v.Put a + v + let put (m : MVar<'a>) v = m.Put v + let take (m : MVar<'a>) = m.Take() + [] + let takeAsync (m : MVar<'a>) = m.TakeAsync () + + type Interlocked with + static member Change(location : byref<'a>, f : 'a -> 'a) = + let mutable initial = location + let mutable computed = f initial + + while Interlocked.CompareExchange(&location, computed, initial) != initial do + initial <- location + computed <- f initial + + computed + + static member Change(location : byref<'a>, f : 'a -> 'a * 'b) = + let mutable initial = location + let (n,r) = f initial + let mutable computed = n + let mutable result = r + + while Interlocked.CompareExchange(&location, computed, initial) != initial do + initial <- location + let (n,r) = f initial + computed <- n + result <- r + + result + + + static member Change(location : byref, f : int -> int) = + let mutable initial = location + let mutable computed = f initial + + while Interlocked.CompareExchange(&location, computed, initial) <> initial do + initial <- location + computed <- f initial + + computed + + static member Change(location : byref, f : int -> int * 'b) = + let mutable initial = location + let (n,r) = f initial + let mutable computed = n + let mutable result = r + + while Interlocked.CompareExchange(&location, computed, initial) <> initial do + initial <- location + let (n,r) = f initial + computed <- n + result <- r + + result + + static member Change(location : byref, f : int64 -> int64) = + let mutable initial = location + let mutable computed = f initial + + while Interlocked.CompareExchange(&location, computed, initial) <> initial do + initial <- location + computed <- f initial + + computed + + static member Change(location : byref, f : int64 -> int64 * 'b) = + let mutable initial = location + let (n,r) = f initial + let mutable computed = n + let mutable result = r + + while Interlocked.CompareExchange(&location, computed, initial) <> initial do + initial <- location + let (n,r) = f initial + computed <- n + result <- r + + result \ No newline at end of file From e7ed0199af9e217b601261daa77c9481981b9fef Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 14:14:47 +0100 Subject: [PATCH 22/45] [FSharp] Move IO utlities to separate file --- .../Aardvark.Base.FSharp.fsproj | 1 + src/Aardvark.Base.FSharp/Utilities/IO.fs | 109 +++++++++++++++++ .../Utilities/Interop/FSLibExtensions.fs | 114 +----------------- 3 files changed, 111 insertions(+), 113 deletions(-) create mode 100644 src/Aardvark.Base.FSharp/Utilities/IO.fs diff --git a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj index 9e97d856..c20eedf1 100644 --- a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj +++ b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj @@ -27,6 +27,7 @@ + diff --git a/src/Aardvark.Base.FSharp/Utilities/IO.fs b/src/Aardvark.Base.FSharp/Utilities/IO.fs new file mode 100644 index 00000000..0dd855ef --- /dev/null +++ b/src/Aardvark.Base.FSharp/Utilities/IO.fs @@ -0,0 +1,109 @@ +namespace Aardvark.Base + +open System.IO + +[] +module IO = + let alterFileName str f = Path.Combine (Path.GetDirectoryName str, f (Path.GetFileName str)) + + let createFileStream path = + if File.Exists path + then File.Delete path + new FileStream(path, FileMode.CreateNew) + + +[] +module Path = + let combine (paths : seq) = Path.Combine(paths |> Seq.toArray) + + let andPath first second = combine [| first; second |] + +[] +module File = + + /// + /// Creates the parent directory of the given file path, if it does not exist. + /// + /// The path of the file, whose parent directory is to be created. + let createParentDirectory (path : string) = + let info = FileInfo(path) + if not info.Directory.Exists then + info.Directory.Create() + + /// + /// Creates a new file, writes the specified string array to the file, and then closes the file. + /// + /// The file to write to. + /// The lines to write to the file. + let writeAllLines (path : string) (lines : string[]) = + File.WriteAllLines(path, lines) + + /// + /// Creates a new file, writes the specified string array to the file, and then closes the file. + /// If the parent directory does not exist, it is created first. + /// + /// The file to write to. + /// The lines to write to the file. + let writeAllLinesSafe (path : string) (lines : string[]) = + createParentDirectory path + File.WriteAllLines(path, lines) + + /// + /// Creates a new file, writes the specified string to the file, and then closes the file. + /// + /// The file to write to. + /// The string to write to the file. + let writeAllText (path : string) (text : string) = + File.WriteAllText(path, text) + + /// + /// Creates a new file, writes the specified string to the file, and then closes the file. + /// If the parent directory does not exist, it is created first. + /// + /// The file to write to. + /// The string to write to the file. + let writeAllTextSafe (path : string) (text : string) = + createParentDirectory path + File.WriteAllText(path, text) + + /// + /// Creates a new file, writes the specified byte array to the file, and then closes the file. + /// + /// The file to write to. + /// The bytes to write to the file. + let writeAllBytes (path : string) (bytes : uint8[]) = + File.WriteAllBytes(path, bytes) + + /// + /// Creates a new file, writes the specified byte array to the file, and then closes the file. + /// If the parent directory does not exist, it is created first. + /// + /// The file to write to. + /// The bytes to write to the file. + let writeAllBytesSafe (path : string) (bytes : uint8[]) = + createParentDirectory path + File.WriteAllBytes(path, bytes) + + /// + /// Opens a text file, reads all lines of the file into a string array, and then closes the file. + /// + /// The file to open for reading. + /// A string array containing all lines of the file. + let readAllLines (path : string) = + File.ReadAllLines path + + /// + /// Opens a text file, reads all the text in the file into a string, and then closes the file. + /// + /// The file to open for reading. + /// A string containing all the text in the file. + let readAllText (path : string) = + File.ReadAllText path + + /// + /// Opens a binary file, reads the contents of the file into a byte array, and then closes the file. + /// + /// The file to open for reading. + /// A byte array containing the contents of the file. + let readAllBytes (path : string) = + File.ReadAllBytes path \ No newline at end of file diff --git a/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs index 6e1f82f1..ae5b3897 100644 --- a/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs +++ b/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs @@ -354,7 +354,7 @@ module Caching = let memoTable = System.Collections.Concurrent.ConcurrentDictionary() let cacheFunction (f : 'a -> 'b) (a : 'a) : 'b = memoTable.GetOrAdd((a,f) :> obj, fun (o:obj) -> f a :> obj) |> unbox<'b> - + [] module NiceUtilities = @@ -391,118 +391,6 @@ module NiceUtilities = | (true, v) -> Some v | _ -> None - -[] -module IO = - open System.IO - - let alterFileName str f = Path.Combine (Path.GetDirectoryName str, f (Path.GetFileName str)) - - let createFileStream path = - if File.Exists path - then File.Delete path - new FileStream(path, FileMode.CreateNew) - - -[] -module Path = - open System.IO - - let combine (paths : seq) = Path.Combine(paths |> Seq.toArray) - - let andPath first second = combine [| first; second |] - -[] -module File = - open System.IO - - /// - /// Creates the parent directory of the given file path, if it does not exist. - /// - /// The path of the file, whose parent directory is to be created. - let createParentDirectory (path : string) = - let info = FileInfo(path) - if not info.Directory.Exists then - info.Directory.Create() - - /// - /// Creates a new file, writes the specified string array to the file, and then closes the file. - /// - /// The file to write to. - /// The lines to write to the file. - let writeAllLines (path : string) (lines : string[]) = - File.WriteAllLines(path, lines) - - /// - /// Creates a new file, writes the specified string array to the file, and then closes the file. - /// If the parent directory does not exist, it is created first. - /// - /// The file to write to. - /// The lines to write to the file. - let writeAllLinesSafe (path : string) (lines : string[]) = - createParentDirectory path - File.WriteAllLines(path, lines) - - /// - /// Creates a new file, writes the specified string to the file, and then closes the file. - /// - /// The file to write to. - /// The string to write to the file. - let writeAllText (path : string) (text : string) = - File.WriteAllText(path, text) - - /// - /// Creates a new file, writes the specified string to the file, and then closes the file. - /// If the parent directory does not exist, it is created first. - /// - /// The file to write to. - /// The string to write to the file. - let writeAllTextSafe (path : string) (text : string) = - createParentDirectory path - File.WriteAllText(path, text) - - /// - /// Creates a new file, writes the specified byte array to the file, and then closes the file. - /// - /// The file to write to. - /// The bytes to write to the file. - let writeAllBytes (path : string) (bytes : uint8[]) = - File.WriteAllBytes(path, bytes) - - /// - /// Creates a new file, writes the specified byte array to the file, and then closes the file. - /// If the parent directory does not exist, it is created first. - /// - /// The file to write to. - /// The bytes to write to the file. - let writeAllBytesSafe (path : string) (bytes : uint8[]) = - createParentDirectory path - File.WriteAllBytes(path, bytes) - - /// - /// Opens a text file, reads all lines of the file into a string array, and then closes the file. - /// - /// The file to open for reading. - /// A string array containing all lines of the file. - let readAllLines (path : string) = - File.ReadAllLines path - - /// - /// Opens a text file, reads all the text in the file into a string, and then closes the file. - /// - /// The file to open for reading. - /// A string containing all the text in the file. - let readAllText (path : string) = - File.ReadAllText path - - /// - /// Opens a binary file, reads the contents of the file into a byte array, and then closes the file. - /// - /// The file to open for reading. - /// A byte array containing the contents of the file. - let readAllBytes (path : string) = - File.ReadAllBytes path - [] module NativeUtilities = open System.Runtime.InteropServices From 6d67952a1c55d53c0f84914e5caea63260e5b061 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 14:20:34 +0100 Subject: [PATCH 23/45] [FSharp] Move native utilities to separate file --- .../Aardvark.Base.FSharp.fsproj | 1 + .../Utilities/Interop/FSLibExtensions.fs | 224 ------------------ src/Aardvark.Base.FSharp/Utilities/Native.fs | 209 ++++++++++++++++ 3 files changed, 210 insertions(+), 224 deletions(-) create mode 100644 src/Aardvark.Base.FSharp/Utilities/Native.fs diff --git a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj index c20eedf1..e7509294 100644 --- a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj +++ b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj @@ -28,6 +28,7 @@ + diff --git a/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs index ae5b3897..0086c027 100644 --- a/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs +++ b/src/Aardvark.Base.FSharp/Utilities/Interop/FSLibExtensions.fs @@ -1,7 +1,6 @@ namespace Aardvark.Base #nowarn "9" -#nowarn "51" open System open FSharp.NativeInterop @@ -391,229 +390,6 @@ module NiceUtilities = | (true, v) -> Some v | _ -> None -[] -module NativeUtilities = - open System.Runtime.InteropServices - open Microsoft.FSharp.NativeInterop - - let private os = System.Environment.OSVersion - let private notimp() = raise <| NotImplementedException() - - - /// - /// MSVCRT wraps memory-functions provided by msvcrt.dll on windows systems. - /// - module internal MSVCRT = - open System - open System.Runtime.InteropServices - - [] - extern nativeint private memcpy_internal(nativeint dest, nativeint src, UIntPtr size); - - [] - extern int private memcmp_internal(nativeint ptr1, nativeint ptr2, UIntPtr size); - - [] - extern nativeint private memset_internal(nativeint ptr, int value, UIntPtr size); - - [] - extern nativeint private memmove_internal(nativeint dest, nativeint src, UIntPtr size); - - - let memcpy(target : nativeint, source : nativeint, size : unativeint) = - memcpy_internal(target, source, size) |> ignore - - let memcmp(ptr1 : nativeint, ptr2 : nativeint, size : unativeint) = - memcmp_internal(ptr1, ptr2, size) - - let memset(ptr : nativeint, value : int, size : unativeint) = - memset_internal(ptr, value, size) |> ignore - - let memmove(target : nativeint, source : nativeint, size : unativeint) = - memmove_internal(target, source, size) |> ignore - - /// - /// LibC wraps memory-functions provided by libc on linux systems. - /// - module internal LibC = - open System - open System.Runtime.InteropServices - - - [] - extern nativeint private memcpy_internal(nativeint dest, nativeint src, UIntPtr size); - - [] - extern int private memcmp_internal(nativeint ptr1, nativeint ptr2, UIntPtr size); - - [] - extern nativeint private memset_internal(nativeint ptr, int value, UIntPtr size); - - [] - extern nativeint private memmove_internal(nativeint dest, nativeint src, UIntPtr size); - - [] - extern int private uname_intern(nativeint buf); - - - let mutable osname = null - let uname() = - if isNull osname then - let ptr : nativeptr = NativePtr.stackalloc 8192 - if uname_intern(NativePtr.toNativeInt ptr) = 0 then - osname <- Marshal.PtrToStringAnsi(NativePtr.toNativeInt ptr) - else - failwith "could not get os-name" - osname - - - - let memcpy(target : nativeint, source : nativeint, size : unativeint) = - memcpy_internal(target, source, size) |> ignore - - let memcmp(ptr1 : nativeint, ptr2 : nativeint, size : unativeint) = - memcmp_internal(ptr1, ptr2, size) - - let memset(ptr : nativeint, value : int, size : unativeint) = - memset_internal(ptr, value, size) |> ignore - - let memmove(target : nativeint, source : nativeint, size : unativeint) = - memmove_internal(target, source, size) |> ignore - - [] - module PlatformStuff = - - - let (|Windows|Linux|Mac|) (p : System.OperatingSystem) = - match p.Platform with - | System.PlatformID.Unix -> - if LibC.uname() = "Darwin" then Mac - else Linux - | System.PlatformID.MacOSX -> Mac - | _ -> Windows - - [] - module NativeInt = - let memcpy (src : nativeint) (dst : nativeint) (size : int) = - match os with - | Windows -> MSVCRT.memcpy(dst, src, unativeint size) - | _ -> LibC.memcpy(dst, src, unativeint size) - - let memmove (src : nativeint) (dst : nativeint) (size : int) = - match os with - | Windows -> MSVCRT.memmove(dst, src, unativeint size) - | _ -> LibC.memmove(dst, src, unativeint size) - - let memset (dst : nativeint) (value : int) (size : int) = - match os with - | Windows -> MSVCRT.memset(dst, value, unativeint size) - | _ -> LibC.memset(dst, value, unativeint size) - - let memcmp (src : nativeint) (dst : nativeint) (size : int) = - match os with - | Windows -> MSVCRT.memcmp(dst, src, unativeint size) - | _ -> LibC.memcmp(dst, src, unativeint size) - - let inline read<'a when 'a : unmanaged> (ptr : nativeint) = - NativePtr.read (NativePtr.ofNativeInt<'a> ptr) - - let inline write<'a when 'a : unmanaged> (ptr : nativeint) (value : 'a) = - NativePtr.write (NativePtr.ofNativeInt<'a> ptr) value - - let inline get<'a when 'a : unmanaged> (ptr : nativeint) (index : int) = - NativePtr.get (NativePtr.ofNativeInt<'a> ptr) index - - let inline set<'a when 'a : unmanaged> (ptr : nativeint) (index : int) (value : 'a)= - NativePtr.set (NativePtr.ofNativeInt<'a> ptr) index value - - type Marshal with - static member Copy(source : nativeint, destination : nativeint, length : unativeint) = - match os with - | Windows -> MSVCRT.memcpy(destination, source, length) - | _ -> LibC.memcpy(destination, source, length) - - static member Move(source : nativeint, destination : nativeint, length : unativeint) = - match os with - | Windows -> MSVCRT.memmove(destination, source, length) - | _ -> LibC.memmove(destination, source, length) - - static member Set(memory : nativeint, value : int, length : unativeint) = - match os with - | Windows -> MSVCRT.memset(memory, value, length) - | _ -> LibC.memset(memory, value, length) - - static member Compare(source : nativeint, destination : nativeint, length : unativeint) = - match os with - | Windows -> MSVCRT.memcmp(destination, source, length) - | _ -> LibC.memcmp(destination, source, length) - - - - static member Copy(source : nativeint, destination : nativeint, length : int) = - Marshal.Copy(source, destination, unativeint length) - - static member Move(source : nativeint, destination : nativeint, length : int) = - Marshal.Move(source, destination, unativeint length) - - static member Set(memory : nativeint, value : int, length : int) = - Marshal.Set(memory, value, unativeint length) - - static member Compare(source : nativeint, destination : nativeint, length : int) = - Marshal.Compare(source, destination, unativeint length) - - - - - static member inline Copy(source : nativeint, destination : nativeint, length : 'a) = - Marshal.Copy(source, destination, unativeint length) - - static member inline Move(source : nativeint, destination : nativeint, length : 'a) = - Marshal.Move(source, destination, unativeint length) - - static member inline Set(memory : nativeint, value : int, length : 'a) = - Marshal.Set(memory, value, unativeint length) - - static member inline Compare(source : nativeint, destination : nativeint, length : 'a) = - Marshal.Compare(source, destination, unativeint length) - - - - - - let pinned (a : obj) f = - let gc = GCHandle.Alloc(a, GCHandleType.Pinned) - try - f ( gc.AddrOfPinnedObject() ) - finally - gc.Free() - -[] -module MarshalDelegateExtensions = - open System.Runtime.InteropServices - open System.Collections.Concurrent - - let private pinnedDelegates = ConcurrentDictionary() - type PinnedDelegate internal(d : Delegate, ptr : nativeint) = - member x.Pointer = ptr - member x.Dispose() = pinnedDelegates.TryRemove d |> ignore - - interface IDisposable with - member x.Dispose() = x.Dispose() - - type Marshal with - static member PinDelegate(d : Delegate) = - let ptr = pinnedDelegates.GetOrAdd(d, fun _ -> Marshal.GetFunctionPointerForDelegate d) - new PinnedDelegate(d, ptr) - - static member PinFunction(f : 'a -> 'b) = - Marshal.PinDelegate(Func<'a, 'b>(f)) - - static member PinFunction(f : 'a -> 'b -> 'c) = - Marshal.PinDelegate(Func<'a, 'b, 'c>(f)) - - static member PinFunction(f : 'a -> 'b -> 'c -> 'd) = - Marshal.PinDelegate(Func<'a, 'b, 'c, 'd>(f)) - module ConversionHelpers = [] diff --git a/src/Aardvark.Base.FSharp/Utilities/Native.fs b/src/Aardvark.Base.FSharp/Utilities/Native.fs new file mode 100644 index 00000000..30a225e2 --- /dev/null +++ b/src/Aardvark.Base.FSharp/Utilities/Native.fs @@ -0,0 +1,209 @@ +namespace Aardvark.Base + +#nowarn "9" + +open System +open System.Runtime.InteropServices +open FSharp.NativeInterop + +[] +module NativeUtilities = + + let private os = System.Environment.OSVersion + + /// + /// MSVCRT wraps memory-functions provided by msvcrt.dll on windows systems. + /// + module internal MSVCRT = + + [] + extern nativeint private memcpy_internal(nativeint dest, nativeint src, UIntPtr size); + + [] + extern int private memcmp_internal(nativeint ptr1, nativeint ptr2, UIntPtr size); + + [] + extern nativeint private memset_internal(nativeint ptr, int value, UIntPtr size); + + [] + extern nativeint private memmove_internal(nativeint dest, nativeint src, UIntPtr size); + + let memcpy(target : nativeint, source : nativeint, size : unativeint) = + memcpy_internal(target, source, size) |> ignore + + let memcmp(ptr1 : nativeint, ptr2 : nativeint, size : unativeint) = + memcmp_internal(ptr1, ptr2, size) + + let memset(ptr : nativeint, value : int, size : unativeint) = + memset_internal(ptr, value, size) |> ignore + + let memmove(target : nativeint, source : nativeint, size : unativeint) = + memmove_internal(target, source, size) |> ignore + + /// + /// LibC wraps memory-functions provided by libc on linux systems. + /// + module internal LibC = + + [] + extern nativeint private memcpy_internal(nativeint dest, nativeint src, UIntPtr size); + + [] + extern int private memcmp_internal(nativeint ptr1, nativeint ptr2, UIntPtr size); + + [] + extern nativeint private memset_internal(nativeint ptr, int value, UIntPtr size); + + [] + extern nativeint private memmove_internal(nativeint dest, nativeint src, UIntPtr size); + + [] + extern int private uname_intern(nativeint buf); + + let mutable osname = null + let uname() = + if isNull osname then + let ptr : nativeptr = NativePtr.stackalloc 8192 + if uname_intern(NativePtr.toNativeInt ptr) = 0 then + osname <- Marshal.PtrToStringAnsi(NativePtr.toNativeInt ptr) + else + failwith "could not get os-name" + osname + + let memcpy(target : nativeint, source : nativeint, size : unativeint) = + memcpy_internal(target, source, size) |> ignore + + let memcmp(ptr1 : nativeint, ptr2 : nativeint, size : unativeint) = + memcmp_internal(ptr1, ptr2, size) + + let memset(ptr : nativeint, value : int, size : unativeint) = + memset_internal(ptr, value, size) |> ignore + + let memmove(target : nativeint, source : nativeint, size : unativeint) = + memmove_internal(target, source, size) |> ignore + + [] + module PlatformStuff = + + let (|Windows|Linux|Mac|) (p : System.OperatingSystem) = + match p.Platform with + | System.PlatformID.Unix -> + if LibC.uname() = "Darwin" then Mac + else Linux + | System.PlatformID.MacOSX -> Mac + | _ -> Windows + + [] + module NativeInt = + let memcpy (src : nativeint) (dst : nativeint) (size : int) = + match os with + | Windows -> MSVCRT.memcpy(dst, src, unativeint size) + | _ -> LibC.memcpy(dst, src, unativeint size) + + let memmove (src : nativeint) (dst : nativeint) (size : int) = + match os with + | Windows -> MSVCRT.memmove(dst, src, unativeint size) + | _ -> LibC.memmove(dst, src, unativeint size) + + let memset (dst : nativeint) (value : int) (size : int) = + match os with + | Windows -> MSVCRT.memset(dst, value, unativeint size) + | _ -> LibC.memset(dst, value, unativeint size) + + let memcmp (src : nativeint) (dst : nativeint) (size : int) = + match os with + | Windows -> MSVCRT.memcmp(dst, src, unativeint size) + | _ -> LibC.memcmp(dst, src, unativeint size) + + let inline read<'a when 'a : unmanaged> (ptr : nativeint) = + NativePtr.read (NativePtr.ofNativeInt<'a> ptr) + + let inline write<'a when 'a : unmanaged> (ptr : nativeint) (value : 'a) = + NativePtr.write (NativePtr.ofNativeInt<'a> ptr) value + + let inline get<'a when 'a : unmanaged> (ptr : nativeint) (index : int) = + NativePtr.get (NativePtr.ofNativeInt<'a> ptr) index + + let inline set<'a when 'a : unmanaged> (ptr : nativeint) (index : int) (value : 'a)= + NativePtr.set (NativePtr.ofNativeInt<'a> ptr) index value + + type Marshal with + static member Copy(source : nativeint, destination : nativeint, length : unativeint) = + match os with + | Windows -> MSVCRT.memcpy(destination, source, length) + | _ -> LibC.memcpy(destination, source, length) + + static member Move(source : nativeint, destination : nativeint, length : unativeint) = + match os with + | Windows -> MSVCRT.memmove(destination, source, length) + | _ -> LibC.memmove(destination, source, length) + + static member Set(memory : nativeint, value : int, length : unativeint) = + match os with + | Windows -> MSVCRT.memset(memory, value, length) + | _ -> LibC.memset(memory, value, length) + + static member Compare(source : nativeint, destination : nativeint, length : unativeint) = + match os with + | Windows -> MSVCRT.memcmp(destination, source, length) + | _ -> LibC.memcmp(destination, source, length) + + + static member Copy(source : nativeint, destination : nativeint, length : int) = + Marshal.Copy(source, destination, unativeint length) + + static member Move(source : nativeint, destination : nativeint, length : int) = + Marshal.Move(source, destination, unativeint length) + + static member Set(memory : nativeint, value : int, length : int) = + Marshal.Set(memory, value, unativeint length) + + static member Compare(source : nativeint, destination : nativeint, length : int) = + Marshal.Compare(source, destination, unativeint length) + + + static member inline Copy(source : nativeint, destination : nativeint, length : 'a) = + Marshal.Copy(source, destination, unativeint length) + + static member inline Move(source : nativeint, destination : nativeint, length : 'a) = + Marshal.Move(source, destination, unativeint length) + + static member inline Set(memory : nativeint, value : int, length : 'a) = + Marshal.Set(memory, value, unativeint length) + + static member inline Compare(source : nativeint, destination : nativeint, length : 'a) = + Marshal.Compare(source, destination, unativeint length) + + + let pinned (a : obj) f = + let gc = GCHandle.Alloc(a, GCHandleType.Pinned) + try + f ( gc.AddrOfPinnedObject() ) + finally + gc.Free() + +[] +module MarshalDelegateExtensions = + open System.Collections.Concurrent + + let private pinnedDelegates = ConcurrentDictionary() + type PinnedDelegate internal(d : Delegate, ptr : nativeint) = + member x.Pointer = ptr + member x.Dispose() = pinnedDelegates.TryRemove d |> ignore + + interface IDisposable with + member x.Dispose() = x.Dispose() + + type Marshal with + static member PinDelegate(d : Delegate) = + let ptr = pinnedDelegates.GetOrAdd(d, fun _ -> Marshal.GetFunctionPointerForDelegate d) + new PinnedDelegate(d, ptr) + + static member PinFunction(f : 'a -> 'b) = + Marshal.PinDelegate(Func<'a, 'b>(f)) + + static member PinFunction(f : 'a -> 'b -> 'c) = + Marshal.PinDelegate(Func<'a, 'b, 'c>(f)) + + static member PinFunction(f : 'a -> 'b -> 'c -> 'd) = + Marshal.PinDelegate(Func<'a, 'b, 'c, 'd>(f)) \ No newline at end of file From fb5b684d70897e88114439fb8ed064bbd376afdf Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 15:01:52 +0100 Subject: [PATCH 24/45] Add Brewer color schemes --- .../Aardvark.Base.FSharp.fsproj | 4 +- src/Aardvark.Base.FSharp/Color/ColorBrewer.fs | 135 ++ .../Color/ColorBrewerSchemes.fs | 1924 +++++++++++++++++ .../Color/ColorBrewerSchemes.fsx | 247 +++ 4 files changed, 2309 insertions(+), 1 deletion(-) create mode 100644 src/Aardvark.Base.FSharp/Color/ColorBrewer.fs create mode 100644 src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fs create mode 100644 src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fsx diff --git a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj index e7509294..3daf3464 100644 --- a/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj +++ b/src/Aardvark.Base.FSharp/Aardvark.Base.FSharp.fsproj @@ -60,6 +60,9 @@ + + + @@ -89,6 +92,5 @@ - \ No newline at end of file diff --git a/src/Aardvark.Base.FSharp/Color/ColorBrewer.fs b/src/Aardvark.Base.FSharp/Color/ColorBrewer.fs new file mode 100644 index 00000000..41b7324d --- /dev/null +++ b/src/Aardvark.Base.FSharp/Color/ColorBrewer.fs @@ -0,0 +1,135 @@ +namespace Aardvark.Base + +open System +open System.Collections +open System.Collections.Generic + +/// Brewer color schemes designed for chloropleth map visualizations. +module ColorBrewer = + + [] + type PaletteUsage = + | None = 0 + + /// Does not confuse people with red-green color blindness. + | ColorBlind = 1 + + /// Suitable for desktop color printing. + | Print = 2 + + /// Suitable for viewing on a laptop LCD display. + /// Small, portable LCD monitors tend to wash-out colors which results in noticeable differences from computer-to-computer. + | LCD = 4 + + /// Withstands black and white photocopying. + /// Diverging schemes can not be photocopied successfully. + /// Differences in lightness should be preserved with sequential schemes. + | PhotoCopy = 8 + + [] + type Palette = + { + /// The color values of the palette. + Colors : C3b[] + + /// Usage properties of the palette. + Usage : PaletteUsage + } + + member inline x.Length = + x.Colors.Length + + member inline x.Item (index : int) = + x.Colors.[index] + + interface IEnumerable with + member x.GetEnumerator() = x.Colors.GetEnumerator() + + interface IEnumerable with + member x.GetEnumerator() = (x.Colors :> IEnumerable).GetEnumerator() + + + type SchemeType = + + /// Diverging schemes put equal emphasis on mid-range critical values and extremes at both ends of the data range. + /// The critical class or break in the middle of the legend is emphasized with light colors and low and high extremes are + /// emphasized with dark colors that have contrasting hues. + | Diverging = 0 + + /// Qualitative schemes do not imply magnitude differences between legend classes, and hues are used to + /// create the primary visual differences between classes. Qualitative schemes are best suited to representing nominal or categorical data. + | Qualitative = 1 + + /// Sequential schemes are suited to ordered data that progress from low to high. + /// Lightness steps dominate the look of these schemes, with light colors for low data values to dark colors for high data values. + | Sequential = 2 + + /// A color scheme containing palettes of various size. + [] + type Scheme = + { + /// Name of the scheme. + Name : Symbol + + /// Type of the scheme. + Type : SchemeType + + /// The palettes of the scheme according to their size. + Palettes : MapExt + } + + /// Returns whether the scheme is empty (i.e. has no palettes). + member inline x.IsEmpty = + x.Palettes.IsEmpty + + /// Size of the smallest palette. + member inline x.MinSize = + x.Palettes.TryMinKeyV |> ValueOption.defaultValue 0 + + /// Size of the largest palette. + member inline x.MaxSize = + x.Palettes.TryMaxKeyV |> ValueOption.defaultValue 0 + + /// Gets the palette with the given size. + /// If the scheme is not defined for the requested size, gets the next larger palette. + /// Throws an exception if the requested size is greater than the maximum size. + member inline x.Item (requestedSize : int) = + match x.Palettes |> MapExt.neighboursV requestedSize with + | struct (_, ValueSome (struct (_, palette)), _) + | struct (_, _, ValueSome (struct (_, palette))) -> + palette + + | struct (ValueSome (struct (max, _)), _, _) -> + raise <| ArgumentOutOfRangeException("requestedSize", $"Scheme {x.Name} has a maximum palette size of {max} (requested {requestedSize}).") + + | struct (ValueNone, ValueNone, ValueNone) -> + raise <| ArgumentException($"Scheme {x.Name} is empty.") + + [] + module Scheme = + + /// Returns whether the scheme is empty (i.e. has no palettes). + let inline isEmpty (scheme : Scheme) = + scheme.IsEmpty + + /// Return the size of the smallest palette for the given scheme. + let inline minSize (scheme : Scheme) = + scheme.MinSize + + /// Return the size of the largest palette for the given scheme. + let inline maxSize (scheme : Scheme) = + scheme.MaxSize + + /// Gets the palette with the given size. + /// If the scheme is not defined for the requested size, gets the next larger palette. + /// Throws an exception if the requested size is greater than the maximum size. + let inline getPalette (requestedSize : int) (scheme : Scheme) = + scheme.[requestedSize] + + /// Returns a new scheme containing only the palettes for which the predicate returns true. + let inline filter (predicate : Palette -> bool) (scheme : Scheme) = + { scheme with Palettes = scheme.Palettes |> MapExt.filter (fun _ -> predicate) } + + /// Returns a new scheme containing only the palettes with the given usage flags. + let inline filterUsage (usage : PaletteUsage) (scheme : Scheme) = + scheme |> filter (fun p -> p.Usage &&& usage = usage) \ No newline at end of file diff --git a/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fs b/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fs new file mode 100644 index 00000000..c2c9bca8 --- /dev/null +++ b/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fs @@ -0,0 +1,1924 @@ +namespace Aardvark.Base + +[] +module ColorBrewerSchemes = + open ColorBrewer + + /// Brewer color schemes designed for chloropleth map visualizations. + module ColorBrewer = + + module Scheme = + + /// Diverging schemes put equal emphasis on mid-range critical values and extremes at both ends of the data range. + /// The critical class or break in the middle of the legend is emphasized with light colors and low and high extremes are + /// emphasized with dark colors that have contrasting hues. + module Diverging = + + let Spectral = + { + Name = Sym.ofString "Spectral" + Type = SchemeType.Diverging + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(252uy,141uy,89uy); C3b(255uy,255uy,191uy); C3b(153uy,213uy,148uy) |] + } + + 4, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(215uy,25uy,28uy); C3b(253uy,174uy,97uy); C3b(171uy,221uy,164uy); C3b(43uy,131uy,186uy) |] + } + + 5, { + Usage = PaletteUsage.Print ||| PaletteUsage.PhotoCopy + Colors = [| C3b(215uy,25uy,28uy); C3b(253uy,174uy,97uy); C3b(255uy,255uy,191uy); C3b(171uy,221uy,164uy) + C3b(43uy,131uy,186uy) |] + } + + 6, { + Usage = PaletteUsage.None + Colors = [| C3b(213uy,62uy,79uy); C3b(252uy,141uy,89uy); C3b(254uy,224uy,139uy); C3b(230uy,245uy,152uy) + C3b(153uy,213uy,148uy); C3b(50uy,136uy,189uy) |] + } + + 7, { + Usage = PaletteUsage.None + Colors = [| C3b(213uy,62uy,79uy); C3b(252uy,141uy,89uy); C3b(254uy,224uy,139uy); C3b(255uy,255uy,191uy) + C3b(230uy,245uy,152uy); C3b(153uy,213uy,148uy); C3b(50uy,136uy,189uy) |] + } + + 8, { + Usage = PaletteUsage.None + Colors = [| C3b(213uy,62uy,79uy); C3b(244uy,109uy,67uy); C3b(253uy,174uy,97uy); C3b(254uy,224uy,139uy) + C3b(230uy,245uy,152uy); C3b(171uy,221uy,164uy); C3b(102uy,194uy,165uy); C3b(50uy,136uy,189uy) |] + } + + 9, { + Usage = PaletteUsage.None + Colors = [| C3b(213uy,62uy,79uy); C3b(244uy,109uy,67uy); C3b(253uy,174uy,97uy); C3b(254uy,224uy,139uy) + C3b(255uy,255uy,191uy); C3b(230uy,245uy,152uy); C3b(171uy,221uy,164uy); C3b(102uy,194uy,165uy) + C3b(50uy,136uy,189uy) |] + } + + 10, { + Usage = PaletteUsage.None + Colors = [| C3b(158uy,1uy,66uy); C3b(213uy,62uy,79uy); C3b(244uy,109uy,67uy); C3b(253uy,174uy,97uy) + C3b(254uy,224uy,139uy); C3b(230uy,245uy,152uy); C3b(171uy,221uy,164uy); C3b(102uy,194uy,165uy) + C3b(50uy,136uy,189uy); C3b(94uy,79uy,162uy) |] + } + + 11, { + Usage = PaletteUsage.None + Colors = [| C3b(158uy,1uy,66uy); C3b(213uy,62uy,79uy); C3b(244uy,109uy,67uy); C3b(253uy,174uy,97uy) + C3b(254uy,224uy,139uy); C3b(255uy,255uy,191uy); C3b(230uy,245uy,152uy); C3b(171uy,221uy,164uy) + C3b(102uy,194uy,165uy); C3b(50uy,136uy,189uy); C3b(94uy,79uy,162uy) |] + } + + ] + } + + let RdYlGn = + { + Name = Sym.ofString "RdYlGn" + Type = SchemeType.Diverging + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(252uy,141uy,89uy); C3b(255uy,255uy,191uy); C3b(145uy,207uy,96uy) |] + } + + 4, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(215uy,25uy,28uy); C3b(253uy,174uy,97uy); C3b(166uy,217uy,106uy); C3b(26uy,150uy,65uy) |] + } + + 5, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(215uy,25uy,28uy); C3b(253uy,174uy,97uy); C3b(255uy,255uy,191uy); C3b(166uy,217uy,106uy) + C3b(26uy,150uy,65uy) |] + } + + 6, { + Usage = PaletteUsage.None + Colors = [| C3b(215uy,48uy,39uy); C3b(252uy,141uy,89uy); C3b(254uy,224uy,139uy); C3b(217uy,239uy,139uy) + C3b(145uy,207uy,96uy); C3b(26uy,152uy,80uy) |] + } + + 7, { + Usage = PaletteUsage.None + Colors = [| C3b(215uy,48uy,39uy); C3b(252uy,141uy,89uy); C3b(254uy,224uy,139uy); C3b(255uy,255uy,191uy) + C3b(217uy,239uy,139uy); C3b(145uy,207uy,96uy); C3b(26uy,152uy,80uy) |] + } + + 8, { + Usage = PaletteUsage.None + Colors = [| C3b(215uy,48uy,39uy); C3b(244uy,109uy,67uy); C3b(253uy,174uy,97uy); C3b(254uy,224uy,139uy) + C3b(217uy,239uy,139uy); C3b(166uy,217uy,106uy); C3b(102uy,189uy,99uy); C3b(26uy,152uy,80uy) |] + } + + 9, { + Usage = PaletteUsage.None + Colors = [| C3b(215uy,48uy,39uy); C3b(244uy,109uy,67uy); C3b(253uy,174uy,97uy); C3b(254uy,224uy,139uy) + C3b(255uy,255uy,191uy); C3b(217uy,239uy,139uy); C3b(166uy,217uy,106uy); C3b(102uy,189uy,99uy) + C3b(26uy,152uy,80uy) |] + } + + 10, { + Usage = PaletteUsage.None + Colors = [| C3b(165uy,0uy,38uy); C3b(215uy,48uy,39uy); C3b(244uy,109uy,67uy); C3b(253uy,174uy,97uy) + C3b(254uy,224uy,139uy); C3b(217uy,239uy,139uy); C3b(166uy,217uy,106uy); C3b(102uy,189uy,99uy) + C3b(26uy,152uy,80uy); C3b(0uy,104uy,55uy) |] + } + + 11, { + Usage = PaletteUsage.None + Colors = [| C3b(165uy,0uy,38uy); C3b(215uy,48uy,39uy); C3b(244uy,109uy,67uy); C3b(253uy,174uy,97uy) + C3b(254uy,224uy,139uy); C3b(255uy,255uy,191uy); C3b(217uy,239uy,139uy); C3b(166uy,217uy,106uy) + C3b(102uy,189uy,99uy); C3b(26uy,152uy,80uy); C3b(0uy,104uy,55uy) |] + } + + ] + } + + let RdBu = + { + Name = Sym.ofString "RdBu" + Type = SchemeType.Diverging + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(239uy,138uy,98uy); C3b(247uy,247uy,247uy); C3b(103uy,169uy,207uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(202uy,0uy,32uy); C3b(244uy,165uy,130uy); C3b(146uy,197uy,222uy); C3b(5uy,113uy,176uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(202uy,0uy,32uy); C3b(244uy,165uy,130uy); C3b(247uy,247uy,247uy); C3b(146uy,197uy,222uy) + C3b(5uy,113uy,176uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print + Colors = [| C3b(178uy,24uy,43uy); C3b(239uy,138uy,98uy); C3b(253uy,219uy,199uy); C3b(209uy,229uy,240uy) + C3b(103uy,169uy,207uy); C3b(33uy,102uy,172uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(178uy,24uy,43uy); C3b(239uy,138uy,98uy); C3b(253uy,219uy,199uy); C3b(247uy,247uy,247uy) + C3b(209uy,229uy,240uy); C3b(103uy,169uy,207uy); C3b(33uy,102uy,172uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(178uy,24uy,43uy); C3b(214uy,96uy,77uy); C3b(244uy,165uy,130uy); C3b(253uy,219uy,199uy) + C3b(209uy,229uy,240uy); C3b(146uy,197uy,222uy); C3b(67uy,147uy,195uy); C3b(33uy,102uy,172uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(178uy,24uy,43uy); C3b(214uy,96uy,77uy); C3b(244uy,165uy,130uy); C3b(253uy,219uy,199uy) + C3b(247uy,247uy,247uy); C3b(209uy,229uy,240uy); C3b(146uy,197uy,222uy); C3b(67uy,147uy,195uy) + C3b(33uy,102uy,172uy) |] + } + + 10, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(103uy,0uy,31uy); C3b(178uy,24uy,43uy); C3b(214uy,96uy,77uy); C3b(244uy,165uy,130uy) + C3b(253uy,219uy,199uy); C3b(209uy,229uy,240uy); C3b(146uy,197uy,222uy); C3b(67uy,147uy,195uy) + C3b(33uy,102uy,172uy); C3b(5uy,48uy,97uy) |] + } + + 11, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(103uy,0uy,31uy); C3b(178uy,24uy,43uy); C3b(214uy,96uy,77uy); C3b(244uy,165uy,130uy) + C3b(253uy,219uy,199uy); C3b(247uy,247uy,247uy); C3b(209uy,229uy,240uy); C3b(146uy,197uy,222uy) + C3b(67uy,147uy,195uy); C3b(33uy,102uy,172uy); C3b(5uy,48uy,97uy) |] + } + + ] + } + + let PiYG = + { + Name = Sym.ofString "PiYG" + Type = SchemeType.Diverging + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(233uy,163uy,201uy); C3b(247uy,247uy,247uy); C3b(161uy,215uy,106uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(208uy,28uy,139uy); C3b(241uy,182uy,218uy); C3b(184uy,225uy,134uy); C3b(77uy,172uy,38uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(208uy,28uy,139uy); C3b(241uy,182uy,218uy); C3b(247uy,247uy,247uy); C3b(184uy,225uy,134uy) + C3b(77uy,172uy,38uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(197uy,27uy,125uy); C3b(233uy,163uy,201uy); C3b(253uy,224uy,239uy); C3b(230uy,245uy,208uy) + C3b(161uy,215uy,106uy); C3b(77uy,146uy,33uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(197uy,27uy,125uy); C3b(233uy,163uy,201uy); C3b(253uy,224uy,239uy); C3b(247uy,247uy,247uy) + C3b(230uy,245uy,208uy); C3b(161uy,215uy,106uy); C3b(77uy,146uy,33uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(197uy,27uy,125uy); C3b(222uy,119uy,174uy); C3b(241uy,182uy,218uy); C3b(253uy,224uy,239uy) + C3b(230uy,245uy,208uy); C3b(184uy,225uy,134uy); C3b(127uy,188uy,65uy); C3b(77uy,146uy,33uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(197uy,27uy,125uy); C3b(222uy,119uy,174uy); C3b(241uy,182uy,218uy); C3b(253uy,224uy,239uy) + C3b(247uy,247uy,247uy); C3b(230uy,245uy,208uy); C3b(184uy,225uy,134uy); C3b(127uy,188uy,65uy) + C3b(77uy,146uy,33uy) |] + } + + 10, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(142uy,1uy,82uy); C3b(197uy,27uy,125uy); C3b(222uy,119uy,174uy); C3b(241uy,182uy,218uy) + C3b(253uy,224uy,239uy); C3b(230uy,245uy,208uy); C3b(184uy,225uy,134uy); C3b(127uy,188uy,65uy) + C3b(77uy,146uy,33uy); C3b(39uy,100uy,25uy) |] + } + + 11, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(142uy,1uy,82uy); C3b(197uy,27uy,125uy); C3b(222uy,119uy,174uy); C3b(241uy,182uy,218uy) + C3b(253uy,224uy,239uy); C3b(247uy,247uy,247uy); C3b(230uy,245uy,208uy); C3b(184uy,225uy,134uy) + C3b(127uy,188uy,65uy); C3b(77uy,146uy,33uy); C3b(39uy,100uy,25uy) |] + } + + ] + } + + let PRGn = + { + Name = Sym.ofString "PRGn" + Type = SchemeType.Diverging + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(175uy,141uy,195uy); C3b(247uy,247uy,247uy); C3b(127uy,191uy,123uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(123uy,50uy,148uy); C3b(194uy,165uy,207uy); C3b(166uy,219uy,160uy); C3b(0uy,136uy,55uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print + Colors = [| C3b(123uy,50uy,148uy); C3b(194uy,165uy,207uy); C3b(247uy,247uy,247uy); C3b(166uy,219uy,160uy) + C3b(0uy,136uy,55uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print + Colors = [| C3b(118uy,42uy,131uy); C3b(175uy,141uy,195uy); C3b(231uy,212uy,232uy); C3b(217uy,240uy,211uy) + C3b(127uy,191uy,123uy); C3b(27uy,120uy,55uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(118uy,42uy,131uy); C3b(175uy,141uy,195uy); C3b(231uy,212uy,232uy); C3b(247uy,247uy,247uy) + C3b(217uy,240uy,211uy); C3b(127uy,191uy,123uy); C3b(27uy,120uy,55uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(118uy,42uy,131uy); C3b(153uy,112uy,171uy); C3b(194uy,165uy,207uy); C3b(231uy,212uy,232uy) + C3b(217uy,240uy,211uy); C3b(166uy,219uy,160uy); C3b(90uy,174uy,97uy); C3b(27uy,120uy,55uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(118uy,42uy,131uy); C3b(153uy,112uy,171uy); C3b(194uy,165uy,207uy); C3b(231uy,212uy,232uy) + C3b(247uy,247uy,247uy); C3b(217uy,240uy,211uy); C3b(166uy,219uy,160uy); C3b(90uy,174uy,97uy) + C3b(27uy,120uy,55uy) |] + } + + 10, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(64uy,0uy,75uy); C3b(118uy,42uy,131uy); C3b(153uy,112uy,171uy); C3b(194uy,165uy,207uy) + C3b(231uy,212uy,232uy); C3b(217uy,240uy,211uy); C3b(166uy,219uy,160uy); C3b(90uy,174uy,97uy) + C3b(27uy,120uy,55uy); C3b(0uy,68uy,27uy) |] + } + + 11, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(64uy,0uy,75uy); C3b(118uy,42uy,131uy); C3b(153uy,112uy,171uy); C3b(194uy,165uy,207uy) + C3b(231uy,212uy,232uy); C3b(247uy,247uy,247uy); C3b(217uy,240uy,211uy); C3b(166uy,219uy,160uy) + C3b(90uy,174uy,97uy); C3b(27uy,120uy,55uy); C3b(0uy,68uy,27uy) |] + } + + ] + } + + let RdYlBu = + { + Name = Sym.ofString "RdYlBu" + Type = SchemeType.Diverging + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(252uy,141uy,89uy); C3b(255uy,255uy,191uy); C3b(145uy,191uy,219uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(215uy,25uy,28uy); C3b(253uy,174uy,97uy); C3b(171uy,217uy,233uy); C3b(44uy,123uy,182uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(215uy,25uy,28uy); C3b(253uy,174uy,97uy); C3b(255uy,255uy,191uy); C3b(171uy,217uy,233uy) + C3b(44uy,123uy,182uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print + Colors = [| C3b(215uy,48uy,39uy); C3b(252uy,141uy,89uy); C3b(254uy,224uy,144uy); C3b(224uy,243uy,248uy) + C3b(145uy,191uy,219uy); C3b(69uy,117uy,180uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(215uy,48uy,39uy); C3b(252uy,141uy,89uy); C3b(254uy,224uy,144uy); C3b(255uy,255uy,191uy) + C3b(224uy,243uy,248uy); C3b(145uy,191uy,219uy); C3b(69uy,117uy,180uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(215uy,48uy,39uy); C3b(244uy,109uy,67uy); C3b(253uy,174uy,97uy); C3b(254uy,224uy,144uy) + C3b(224uy,243uy,248uy); C3b(171uy,217uy,233uy); C3b(116uy,173uy,209uy); C3b(69uy,117uy,180uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(215uy,48uy,39uy); C3b(244uy,109uy,67uy); C3b(253uy,174uy,97uy); C3b(254uy,224uy,144uy) + C3b(255uy,255uy,191uy); C3b(224uy,243uy,248uy); C3b(171uy,217uy,233uy); C3b(116uy,173uy,209uy) + C3b(69uy,117uy,180uy) |] + } + + 10, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(165uy,0uy,38uy); C3b(215uy,48uy,39uy); C3b(244uy,109uy,67uy); C3b(253uy,174uy,97uy) + C3b(254uy,224uy,144uy); C3b(224uy,243uy,248uy); C3b(171uy,217uy,233uy); C3b(116uy,173uy,209uy) + C3b(69uy,117uy,180uy); C3b(49uy,54uy,149uy) |] + } + + 11, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(165uy,0uy,38uy); C3b(215uy,48uy,39uy); C3b(244uy,109uy,67uy); C3b(253uy,174uy,97uy) + C3b(254uy,224uy,144uy); C3b(255uy,255uy,191uy); C3b(224uy,243uy,248uy); C3b(171uy,217uy,233uy) + C3b(116uy,173uy,209uy); C3b(69uy,117uy,180uy); C3b(49uy,54uy,149uy) |] + } + + ] + } + + let BrBG = + { + Name = Sym.ofString "BrBG" + Type = SchemeType.Diverging + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(216uy,179uy,101uy); C3b(245uy,245uy,245uy); C3b(90uy,180uy,172uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(166uy,97uy,26uy); C3b(223uy,194uy,125uy); C3b(128uy,205uy,193uy); C3b(1uy,133uy,113uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(166uy,97uy,26uy); C3b(223uy,194uy,125uy); C3b(245uy,245uy,245uy); C3b(128uy,205uy,193uy) + C3b(1uy,133uy,113uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(140uy,81uy,10uy); C3b(216uy,179uy,101uy); C3b(246uy,232uy,195uy); C3b(199uy,234uy,229uy) + C3b(90uy,180uy,172uy); C3b(1uy,102uy,94uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(140uy,81uy,10uy); C3b(216uy,179uy,101uy); C3b(246uy,232uy,195uy); C3b(245uy,245uy,245uy) + C3b(199uy,234uy,229uy); C3b(90uy,180uy,172uy); C3b(1uy,102uy,94uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(140uy,81uy,10uy); C3b(191uy,129uy,45uy); C3b(223uy,194uy,125uy); C3b(246uy,232uy,195uy) + C3b(199uy,234uy,229uy); C3b(128uy,205uy,193uy); C3b(53uy,151uy,143uy); C3b(1uy,102uy,94uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(140uy,81uy,10uy); C3b(191uy,129uy,45uy); C3b(223uy,194uy,125uy); C3b(246uy,232uy,195uy) + C3b(245uy,245uy,245uy); C3b(199uy,234uy,229uy); C3b(128uy,205uy,193uy); C3b(53uy,151uy,143uy) + C3b(1uy,102uy,94uy) |] + } + + 10, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(84uy,48uy,5uy); C3b(140uy,81uy,10uy); C3b(191uy,129uy,45uy); C3b(223uy,194uy,125uy) + C3b(246uy,232uy,195uy); C3b(199uy,234uy,229uy); C3b(128uy,205uy,193uy); C3b(53uy,151uy,143uy) + C3b(1uy,102uy,94uy); C3b(0uy,60uy,48uy) |] + } + + 11, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(84uy,48uy,5uy); C3b(140uy,81uy,10uy); C3b(191uy,129uy,45uy); C3b(223uy,194uy,125uy) + C3b(246uy,232uy,195uy); C3b(245uy,245uy,245uy); C3b(199uy,234uy,229uy); C3b(128uy,205uy,193uy) + C3b(53uy,151uy,143uy); C3b(1uy,102uy,94uy); C3b(0uy,60uy,48uy) |] + } + + ] + } + + let RdGy = + { + Name = Sym.ofString "RdGy" + Type = SchemeType.Diverging + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(239uy,138uy,98uy); C3b(255uy,255uy,255uy); C3b(153uy,153uy,153uy) |] + } + + 4, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(202uy,0uy,32uy); C3b(244uy,165uy,130uy); C3b(186uy,186uy,186uy); C3b(64uy,64uy,64uy) |] + } + + 5, { + Usage = PaletteUsage.Print + Colors = [| C3b(202uy,0uy,32uy); C3b(244uy,165uy,130uy); C3b(255uy,255uy,255uy); C3b(186uy,186uy,186uy) + C3b(64uy,64uy,64uy) |] + } + + 6, { + Usage = PaletteUsage.None + Colors = [| C3b(178uy,24uy,43uy); C3b(239uy,138uy,98uy); C3b(253uy,219uy,199uy); C3b(224uy,224uy,224uy) + C3b(153uy,153uy,153uy); C3b(77uy,77uy,77uy) |] + } + + 7, { + Usage = PaletteUsage.None + Colors = [| C3b(178uy,24uy,43uy); C3b(239uy,138uy,98uy); C3b(253uy,219uy,199uy); C3b(255uy,255uy,255uy) + C3b(224uy,224uy,224uy); C3b(153uy,153uy,153uy); C3b(77uy,77uy,77uy) |] + } + + 8, { + Usage = PaletteUsage.None + Colors = [| C3b(178uy,24uy,43uy); C3b(214uy,96uy,77uy); C3b(244uy,165uy,130uy); C3b(253uy,219uy,199uy) + C3b(224uy,224uy,224uy); C3b(186uy,186uy,186uy); C3b(135uy,135uy,135uy); C3b(77uy,77uy,77uy) |] + } + + 9, { + Usage = PaletteUsage.None + Colors = [| C3b(178uy,24uy,43uy); C3b(214uy,96uy,77uy); C3b(244uy,165uy,130uy); C3b(253uy,219uy,199uy) + C3b(255uy,255uy,255uy); C3b(224uy,224uy,224uy); C3b(186uy,186uy,186uy); C3b(135uy,135uy,135uy) + C3b(77uy,77uy,77uy) |] + } + + 10, { + Usage = PaletteUsage.None + Colors = [| C3b(103uy,0uy,31uy); C3b(178uy,24uy,43uy); C3b(214uy,96uy,77uy); C3b(244uy,165uy,130uy) + C3b(253uy,219uy,199uy); C3b(224uy,224uy,224uy); C3b(186uy,186uy,186uy); C3b(135uy,135uy,135uy) + C3b(77uy,77uy,77uy); C3b(26uy,26uy,26uy) |] + } + + 11, { + Usage = PaletteUsage.None + Colors = [| C3b(103uy,0uy,31uy); C3b(178uy,24uy,43uy); C3b(214uy,96uy,77uy); C3b(244uy,165uy,130uy) + C3b(253uy,219uy,199uy); C3b(255uy,255uy,255uy); C3b(224uy,224uy,224uy); C3b(186uy,186uy,186uy) + C3b(135uy,135uy,135uy); C3b(77uy,77uy,77uy); C3b(26uy,26uy,26uy) |] + } + + ] + } + + let PuOr = + { + Name = Sym.ofString "PuOr" + Type = SchemeType.Diverging + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(241uy,163uy,64uy); C3b(247uy,247uy,247uy); C3b(153uy,142uy,195uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(230uy,97uy,1uy); C3b(253uy,184uy,99uy); C3b(178uy,171uy,210uy); C3b(94uy,60uy,153uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.LCD + Colors = [| C3b(230uy,97uy,1uy); C3b(253uy,184uy,99uy); C3b(247uy,247uy,247uy); C3b(178uy,171uy,210uy) + C3b(94uy,60uy,153uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.LCD + Colors = [| C3b(179uy,88uy,6uy); C3b(241uy,163uy,64uy); C3b(254uy,224uy,182uy); C3b(216uy,218uy,235uy) + C3b(153uy,142uy,195uy); C3b(84uy,39uy,136uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(179uy,88uy,6uy); C3b(241uy,163uy,64uy); C3b(254uy,224uy,182uy); C3b(247uy,247uy,247uy) + C3b(216uy,218uy,235uy); C3b(153uy,142uy,195uy); C3b(84uy,39uy,136uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(179uy,88uy,6uy); C3b(224uy,130uy,20uy); C3b(253uy,184uy,99uy); C3b(254uy,224uy,182uy) + C3b(216uy,218uy,235uy); C3b(178uy,171uy,210uy); C3b(128uy,115uy,172uy); C3b(84uy,39uy,136uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(179uy,88uy,6uy); C3b(224uy,130uy,20uy); C3b(253uy,184uy,99uy); C3b(254uy,224uy,182uy) + C3b(247uy,247uy,247uy); C3b(216uy,218uy,235uy); C3b(178uy,171uy,210uy); C3b(128uy,115uy,172uy) + C3b(84uy,39uy,136uy) |] + } + + 10, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(127uy,59uy,8uy); C3b(179uy,88uy,6uy); C3b(224uy,130uy,20uy); C3b(253uy,184uy,99uy) + C3b(254uy,224uy,182uy); C3b(216uy,218uy,235uy); C3b(178uy,171uy,210uy); C3b(128uy,115uy,172uy) + C3b(84uy,39uy,136uy); C3b(45uy,0uy,75uy) |] + } + + 11, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(127uy,59uy,8uy); C3b(179uy,88uy,6uy); C3b(224uy,130uy,20uy); C3b(253uy,184uy,99uy) + C3b(254uy,224uy,182uy); C3b(247uy,247uy,247uy); C3b(216uy,218uy,235uy); C3b(178uy,171uy,210uy) + C3b(128uy,115uy,172uy); C3b(84uy,39uy,136uy); C3b(45uy,0uy,75uy) |] + } + + ] + } + + /// Qualitative schemes do not imply magnitude differences between legend classes, and hues are used to + /// create the primary visual differences between classes. Qualitative schemes are best suited to representing nominal or categorical data. + module Qualitative = + + let Set2 = + { + Name = Sym.ofString "Set2" + Type = SchemeType.Qualitative + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(102uy,194uy,165uy); C3b(252uy,141uy,98uy); C3b(141uy,160uy,203uy) |] + } + + 4, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(102uy,194uy,165uy); C3b(252uy,141uy,98uy); C3b(141uy,160uy,203uy); C3b(231uy,138uy,195uy) |] + } + + 5, { + Usage = PaletteUsage.Print + Colors = [| C3b(102uy,194uy,165uy); C3b(252uy,141uy,98uy); C3b(141uy,160uy,203uy); C3b(231uy,138uy,195uy) + C3b(166uy,216uy,84uy) |] + } + + 6, { + Usage = PaletteUsage.None + Colors = [| C3b(102uy,194uy,165uy); C3b(252uy,141uy,98uy); C3b(141uy,160uy,203uy); C3b(231uy,138uy,195uy) + C3b(166uy,216uy,84uy); C3b(255uy,217uy,47uy) |] + } + + 7, { + Usage = PaletteUsage.None + Colors = [| C3b(102uy,194uy,165uy); C3b(252uy,141uy,98uy); C3b(141uy,160uy,203uy); C3b(231uy,138uy,195uy) + C3b(166uy,216uy,84uy); C3b(255uy,217uy,47uy); C3b(229uy,196uy,148uy) |] + } + + 8, { + Usage = PaletteUsage.None + Colors = [| C3b(102uy,194uy,165uy); C3b(252uy,141uy,98uy); C3b(141uy,160uy,203uy); C3b(231uy,138uy,195uy) + C3b(166uy,216uy,84uy); C3b(255uy,217uy,47uy); C3b(229uy,196uy,148uy); C3b(179uy,179uy,179uy) |] + } + + ] + } + + let Accent = + { + Name = Sym.ofString "Accent" + Type = SchemeType.Qualitative + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(127uy,201uy,127uy); C3b(190uy,174uy,212uy); C3b(253uy,192uy,134uy) |] + } + + 4, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(127uy,201uy,127uy); C3b(190uy,174uy,212uy); C3b(253uy,192uy,134uy); C3b(255uy,255uy,153uy) |] + } + + 5, { + Usage = PaletteUsage.LCD + Colors = [| C3b(127uy,201uy,127uy); C3b(190uy,174uy,212uy); C3b(253uy,192uy,134uy); C3b(255uy,255uy,153uy) + C3b(56uy,108uy,176uy) |] + } + + 6, { + Usage = PaletteUsage.None + Colors = [| C3b(127uy,201uy,127uy); C3b(190uy,174uy,212uy); C3b(253uy,192uy,134uy); C3b(255uy,255uy,153uy) + C3b(56uy,108uy,176uy); C3b(240uy,2uy,127uy) |] + } + + 7, { + Usage = PaletteUsage.None + Colors = [| C3b(127uy,201uy,127uy); C3b(190uy,174uy,212uy); C3b(253uy,192uy,134uy); C3b(255uy,255uy,153uy) + C3b(56uy,108uy,176uy); C3b(240uy,2uy,127uy); C3b(191uy,91uy,23uy) |] + } + + 8, { + Usage = PaletteUsage.None + Colors = [| C3b(127uy,201uy,127uy); C3b(190uy,174uy,212uy); C3b(253uy,192uy,134uy); C3b(255uy,255uy,153uy) + C3b(56uy,108uy,176uy); C3b(240uy,2uy,127uy); C3b(191uy,91uy,23uy); C3b(102uy,102uy,102uy) |] + } + + ] + } + + let Set1 = + { + Name = Sym.ofString "Set1" + Type = SchemeType.Qualitative + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(228uy,26uy,28uy); C3b(55uy,126uy,184uy); C3b(77uy,175uy,74uy) |] + } + + 4, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(228uy,26uy,28uy); C3b(55uy,126uy,184uy); C3b(77uy,175uy,74uy); C3b(152uy,78uy,163uy) |] + } + + 5, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(228uy,26uy,28uy); C3b(55uy,126uy,184uy); C3b(77uy,175uy,74uy); C3b(152uy,78uy,163uy) + C3b(255uy,127uy,0uy) |] + } + + 6, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(228uy,26uy,28uy); C3b(55uy,126uy,184uy); C3b(77uy,175uy,74uy); C3b(152uy,78uy,163uy) + C3b(255uy,127uy,0uy); C3b(255uy,255uy,51uy) |] + } + + 7, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(228uy,26uy,28uy); C3b(55uy,126uy,184uy); C3b(77uy,175uy,74uy); C3b(152uy,78uy,163uy) + C3b(255uy,127uy,0uy); C3b(255uy,255uy,51uy); C3b(166uy,86uy,40uy) |] + } + + 8, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(228uy,26uy,28uy); C3b(55uy,126uy,184uy); C3b(77uy,175uy,74uy); C3b(152uy,78uy,163uy) + C3b(255uy,127uy,0uy); C3b(255uy,255uy,51uy); C3b(166uy,86uy,40uy); C3b(247uy,129uy,191uy) |] + } + + 9, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(228uy,26uy,28uy); C3b(55uy,126uy,184uy); C3b(77uy,175uy,74uy); C3b(152uy,78uy,163uy) + C3b(255uy,127uy,0uy); C3b(255uy,255uy,51uy); C3b(166uy,86uy,40uy); C3b(247uy,129uy,191uy) + C3b(153uy,153uy,153uy) |] + } + + ] + } + + let Set3 = + { + Name = Sym.ofString "Set3" + Type = SchemeType.Qualitative + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(141uy,211uy,199uy); C3b(255uy,255uy,179uy); C3b(190uy,186uy,218uy) |] + } + + 4, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(141uy,211uy,199uy); C3b(255uy,255uy,179uy); C3b(190uy,186uy,218uy); C3b(251uy,128uy,114uy) |] + } + + 5, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(141uy,211uy,199uy); C3b(255uy,255uy,179uy); C3b(190uy,186uy,218uy); C3b(251uy,128uy,114uy) + C3b(128uy,177uy,211uy) |] + } + + 6, { + Usage = PaletteUsage.Print + Colors = [| C3b(141uy,211uy,199uy); C3b(255uy,255uy,179uy); C3b(190uy,186uy,218uy); C3b(251uy,128uy,114uy) + C3b(128uy,177uy,211uy); C3b(253uy,180uy,98uy) |] + } + + 7, { + Usage = PaletteUsage.Print + Colors = [| C3b(141uy,211uy,199uy); C3b(255uy,255uy,179uy); C3b(190uy,186uy,218uy); C3b(251uy,128uy,114uy) + C3b(128uy,177uy,211uy); C3b(253uy,180uy,98uy); C3b(179uy,222uy,105uy) |] + } + + 8, { + Usage = PaletteUsage.Print + Colors = [| C3b(141uy,211uy,199uy); C3b(255uy,255uy,179uy); C3b(190uy,186uy,218uy); C3b(251uy,128uy,114uy) + C3b(128uy,177uy,211uy); C3b(253uy,180uy,98uy); C3b(179uy,222uy,105uy); C3b(252uy,205uy,229uy) |] + } + + 9, { + Usage = PaletteUsage.None + Colors = [| C3b(141uy,211uy,199uy); C3b(255uy,255uy,179uy); C3b(190uy,186uy,218uy); C3b(251uy,128uy,114uy) + C3b(128uy,177uy,211uy); C3b(253uy,180uy,98uy); C3b(179uy,222uy,105uy); C3b(252uy,205uy,229uy) + C3b(217uy,217uy,217uy) |] + } + + 10, { + Usage = PaletteUsage.None + Colors = [| C3b(141uy,211uy,199uy); C3b(255uy,255uy,179uy); C3b(190uy,186uy,218uy); C3b(251uy,128uy,114uy) + C3b(128uy,177uy,211uy); C3b(253uy,180uy,98uy); C3b(179uy,222uy,105uy); C3b(252uy,205uy,229uy) + C3b(217uy,217uy,217uy); C3b(188uy,128uy,189uy) |] + } + + 11, { + Usage = PaletteUsage.None + Colors = [| C3b(141uy,211uy,199uy); C3b(255uy,255uy,179uy); C3b(190uy,186uy,218uy); C3b(251uy,128uy,114uy) + C3b(128uy,177uy,211uy); C3b(253uy,180uy,98uy); C3b(179uy,222uy,105uy); C3b(252uy,205uy,229uy) + C3b(217uy,217uy,217uy); C3b(188uy,128uy,189uy); C3b(204uy,235uy,197uy) |] + } + + 12, { + Usage = PaletteUsage.None + Colors = [| C3b(141uy,211uy,199uy); C3b(255uy,255uy,179uy); C3b(190uy,186uy,218uy); C3b(251uy,128uy,114uy) + C3b(128uy,177uy,211uy); C3b(253uy,180uy,98uy); C3b(179uy,222uy,105uy); C3b(252uy,205uy,229uy) + C3b(217uy,217uy,217uy); C3b(188uy,128uy,189uy); C3b(204uy,235uy,197uy); C3b(255uy,237uy,111uy) |] + } + + ] + } + + let Dark2 = + { + Name = Sym.ofString "Dark2" + Type = SchemeType.Qualitative + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(27uy,158uy,119uy); C3b(217uy,95uy,2uy); C3b(117uy,112uy,179uy) |] + } + + 4, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(27uy,158uy,119uy); C3b(217uy,95uy,2uy); C3b(117uy,112uy,179uy); C3b(231uy,41uy,138uy) |] + } + + 5, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(27uy,158uy,119uy); C3b(217uy,95uy,2uy); C3b(117uy,112uy,179uy); C3b(231uy,41uy,138uy) + C3b(102uy,166uy,30uy) |] + } + + 6, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(27uy,158uy,119uy); C3b(217uy,95uy,2uy); C3b(117uy,112uy,179uy); C3b(231uy,41uy,138uy) + C3b(102uy,166uy,30uy); C3b(230uy,171uy,2uy) |] + } + + 7, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(27uy,158uy,119uy); C3b(217uy,95uy,2uy); C3b(117uy,112uy,179uy); C3b(231uy,41uy,138uy) + C3b(102uy,166uy,30uy); C3b(230uy,171uy,2uy); C3b(166uy,118uy,29uy) |] + } + + 8, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(27uy,158uy,119uy); C3b(217uy,95uy,2uy); C3b(117uy,112uy,179uy); C3b(231uy,41uy,138uy) + C3b(102uy,166uy,30uy); C3b(230uy,171uy,2uy); C3b(166uy,118uy,29uy); C3b(102uy,102uy,102uy) |] + } + + ] + } + + let Paired = + { + Name = Sym.ofString "Paired" + Type = SchemeType.Qualitative + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(166uy,206uy,227uy); C3b(31uy,120uy,180uy); C3b(178uy,223uy,138uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(166uy,206uy,227uy); C3b(31uy,120uy,180uy); C3b(178uy,223uy,138uy); C3b(51uy,160uy,44uy) |] + } + + 5, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(166uy,206uy,227uy); C3b(31uy,120uy,180uy); C3b(178uy,223uy,138uy); C3b(51uy,160uy,44uy) + C3b(251uy,154uy,153uy) |] + } + + 6, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(166uy,206uy,227uy); C3b(31uy,120uy,180uy); C3b(178uy,223uy,138uy); C3b(51uy,160uy,44uy) + C3b(251uy,154uy,153uy); C3b(227uy,26uy,28uy) |] + } + + 7, { + Usage = PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(166uy,206uy,227uy); C3b(31uy,120uy,180uy); C3b(178uy,223uy,138uy); C3b(51uy,160uy,44uy) + C3b(251uy,154uy,153uy); C3b(227uy,26uy,28uy); C3b(253uy,191uy,111uy) |] + } + + 8, { + Usage = PaletteUsage.LCD + Colors = [| C3b(166uy,206uy,227uy); C3b(31uy,120uy,180uy); C3b(178uy,223uy,138uy); C3b(51uy,160uy,44uy) + C3b(251uy,154uy,153uy); C3b(227uy,26uy,28uy); C3b(253uy,191uy,111uy); C3b(255uy,127uy,0uy) |] + } + + 9, { + Usage = PaletteUsage.LCD + Colors = [| C3b(166uy,206uy,227uy); C3b(31uy,120uy,180uy); C3b(178uy,223uy,138uy); C3b(51uy,160uy,44uy) + C3b(251uy,154uy,153uy); C3b(227uy,26uy,28uy); C3b(253uy,191uy,111uy); C3b(255uy,127uy,0uy) + C3b(202uy,178uy,214uy) |] + } + + 10, { + Usage = PaletteUsage.LCD + Colors = [| C3b(166uy,206uy,227uy); C3b(31uy,120uy,180uy); C3b(178uy,223uy,138uy); C3b(51uy,160uy,44uy) + C3b(251uy,154uy,153uy); C3b(227uy,26uy,28uy); C3b(253uy,191uy,111uy); C3b(255uy,127uy,0uy) + C3b(202uy,178uy,214uy); C3b(106uy,61uy,154uy) |] + } + + 11, { + Usage = PaletteUsage.None + Colors = [| C3b(166uy,206uy,227uy); C3b(31uy,120uy,180uy); C3b(178uy,223uy,138uy); C3b(51uy,160uy,44uy) + C3b(251uy,154uy,153uy); C3b(227uy,26uy,28uy); C3b(253uy,191uy,111uy); C3b(255uy,127uy,0uy) + C3b(202uy,178uy,214uy); C3b(106uy,61uy,154uy); C3b(255uy,255uy,153uy) |] + } + + 12, { + Usage = PaletteUsage.None + Colors = [| C3b(166uy,206uy,227uy); C3b(31uy,120uy,180uy); C3b(178uy,223uy,138uy); C3b(51uy,160uy,44uy) + C3b(251uy,154uy,153uy); C3b(227uy,26uy,28uy); C3b(253uy,191uy,111uy); C3b(255uy,127uy,0uy) + C3b(202uy,178uy,214uy); C3b(106uy,61uy,154uy); C3b(255uy,255uy,153uy); C3b(177uy,89uy,40uy) |] + } + + ] + } + + let Pastel2 = + { + Name = Sym.ofString "Pastel2" + Type = SchemeType.Qualitative + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.None + Colors = [| C3b(179uy,226uy,205uy); C3b(253uy,205uy,172uy); C3b(203uy,213uy,232uy) |] + } + + 4, { + Usage = PaletteUsage.None + Colors = [| C3b(179uy,226uy,205uy); C3b(253uy,205uy,172uy); C3b(203uy,213uy,232uy); C3b(244uy,202uy,228uy) |] + } + + 5, { + Usage = PaletteUsage.None + Colors = [| C3b(179uy,226uy,205uy); C3b(253uy,205uy,172uy); C3b(203uy,213uy,232uy); C3b(244uy,202uy,228uy) + C3b(230uy,245uy,201uy) |] + } + + 6, { + Usage = PaletteUsage.None + Colors = [| C3b(179uy,226uy,205uy); C3b(253uy,205uy,172uy); C3b(203uy,213uy,232uy); C3b(244uy,202uy,228uy) + C3b(230uy,245uy,201uy); C3b(255uy,242uy,174uy) |] + } + + 7, { + Usage = PaletteUsage.None + Colors = [| C3b(179uy,226uy,205uy); C3b(253uy,205uy,172uy); C3b(203uy,213uy,232uy); C3b(244uy,202uy,228uy) + C3b(230uy,245uy,201uy); C3b(255uy,242uy,174uy); C3b(241uy,226uy,204uy) |] + } + + 8, { + Usage = PaletteUsage.None + Colors = [| C3b(179uy,226uy,205uy); C3b(253uy,205uy,172uy); C3b(203uy,213uy,232uy); C3b(244uy,202uy,228uy) + C3b(230uy,245uy,201uy); C3b(255uy,242uy,174uy); C3b(241uy,226uy,204uy); C3b(204uy,204uy,204uy) |] + } + + ] + } + + let Pastel1 = + { + Name = Sym.ofString "Pastel1" + Type = SchemeType.Qualitative + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.None + Colors = [| C3b(251uy,180uy,174uy); C3b(179uy,205uy,227uy); C3b(204uy,235uy,197uy) |] + } + + 4, { + Usage = PaletteUsage.None + Colors = [| C3b(251uy,180uy,174uy); C3b(179uy,205uy,227uy); C3b(204uy,235uy,197uy); C3b(222uy,203uy,228uy) |] + } + + 5, { + Usage = PaletteUsage.None + Colors = [| C3b(251uy,180uy,174uy); C3b(179uy,205uy,227uy); C3b(204uy,235uy,197uy); C3b(222uy,203uy,228uy) + C3b(254uy,217uy,166uy) |] + } + + 6, { + Usage = PaletteUsage.None + Colors = [| C3b(251uy,180uy,174uy); C3b(179uy,205uy,227uy); C3b(204uy,235uy,197uy); C3b(222uy,203uy,228uy) + C3b(254uy,217uy,166uy); C3b(255uy,255uy,204uy) |] + } + + 7, { + Usage = PaletteUsage.None + Colors = [| C3b(251uy,180uy,174uy); C3b(179uy,205uy,227uy); C3b(204uy,235uy,197uy); C3b(222uy,203uy,228uy) + C3b(254uy,217uy,166uy); C3b(255uy,255uy,204uy); C3b(229uy,216uy,189uy) |] + } + + 8, { + Usage = PaletteUsage.None + Colors = [| C3b(251uy,180uy,174uy); C3b(179uy,205uy,227uy); C3b(204uy,235uy,197uy); C3b(222uy,203uy,228uy) + C3b(254uy,217uy,166uy); C3b(255uy,255uy,204uy); C3b(229uy,216uy,189uy); C3b(253uy,218uy,236uy) |] + } + + 9, { + Usage = PaletteUsage.None + Colors = [| C3b(251uy,180uy,174uy); C3b(179uy,205uy,227uy); C3b(204uy,235uy,197uy); C3b(222uy,203uy,228uy) + C3b(254uy,217uy,166uy); C3b(255uy,255uy,204uy); C3b(229uy,216uy,189uy); C3b(253uy,218uy,236uy) + C3b(242uy,242uy,242uy) |] + } + + ] + } + + /// Sequential schemes are suited to ordered data that progress from low to high. + /// Lightness steps dominate the look of these schemes, with light colors for low data values to dark colors for high data values. + module Sequential = + + let OrRd = + { + Name = Sym.ofString "OrRd" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(254uy,232uy,200uy); C3b(253uy,187uy,132uy); C3b(227uy,74uy,51uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(254uy,240uy,217uy); C3b(253uy,204uy,138uy); C3b(252uy,141uy,89uy); C3b(215uy,48uy,31uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.LCD + Colors = [| C3b(254uy,240uy,217uy); C3b(253uy,204uy,138uy); C3b(252uy,141uy,89uy); C3b(227uy,74uy,51uy) + C3b(179uy,0uy,0uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(254uy,240uy,217uy); C3b(253uy,212uy,158uy); C3b(253uy,187uy,132uy); C3b(252uy,141uy,89uy) + C3b(227uy,74uy,51uy); C3b(179uy,0uy,0uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(254uy,240uy,217uy); C3b(253uy,212uy,158uy); C3b(253uy,187uy,132uy); C3b(252uy,141uy,89uy) + C3b(239uy,101uy,72uy); C3b(215uy,48uy,31uy); C3b(153uy,0uy,0uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,247uy,236uy); C3b(254uy,232uy,200uy); C3b(253uy,212uy,158uy); C3b(253uy,187uy,132uy) + C3b(252uy,141uy,89uy); C3b(239uy,101uy,72uy); C3b(215uy,48uy,31uy); C3b(153uy,0uy,0uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,247uy,236uy); C3b(254uy,232uy,200uy); C3b(253uy,212uy,158uy); C3b(253uy,187uy,132uy) + C3b(252uy,141uy,89uy); C3b(239uy,101uy,72uy); C3b(215uy,48uy,31uy); C3b(179uy,0uy,0uy) + C3b(127uy,0uy,0uy) |] + } + + ] + } + + let PuBu = + { + Name = Sym.ofString "PuBu" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(236uy,231uy,242uy); C3b(166uy,189uy,219uy); C3b(43uy,140uy,190uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.LCD + Colors = [| C3b(241uy,238uy,246uy); C3b(189uy,201uy,225uy); C3b(116uy,169uy,207uy); C3b(5uy,112uy,176uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(241uy,238uy,246uy); C3b(189uy,201uy,225uy); C3b(116uy,169uy,207uy); C3b(43uy,140uy,190uy) + C3b(4uy,90uy,141uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(241uy,238uy,246uy); C3b(208uy,209uy,230uy); C3b(166uy,189uy,219uy); C3b(116uy,169uy,207uy) + C3b(43uy,140uy,190uy); C3b(4uy,90uy,141uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(241uy,238uy,246uy); C3b(208uy,209uy,230uy); C3b(166uy,189uy,219uy); C3b(116uy,169uy,207uy) + C3b(54uy,144uy,192uy); C3b(5uy,112uy,176uy); C3b(3uy,78uy,123uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,247uy,251uy); C3b(236uy,231uy,242uy); C3b(208uy,209uy,230uy); C3b(166uy,189uy,219uy) + C3b(116uy,169uy,207uy); C3b(54uy,144uy,192uy); C3b(5uy,112uy,176uy); C3b(3uy,78uy,123uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,247uy,251uy); C3b(236uy,231uy,242uy); C3b(208uy,209uy,230uy); C3b(166uy,189uy,219uy) + C3b(116uy,169uy,207uy); C3b(54uy,144uy,192uy); C3b(5uy,112uy,176uy); C3b(4uy,90uy,141uy) + C3b(2uy,56uy,88uy) |] + } + + ] + } + + let BuPu = + { + Name = Sym.ofString "BuPu" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(224uy,236uy,244uy); C3b(158uy,188uy,218uy); C3b(136uy,86uy,167uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(237uy,248uy,251uy); C3b(179uy,205uy,227uy); C3b(140uy,150uy,198uy); C3b(136uy,65uy,157uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.LCD + Colors = [| C3b(237uy,248uy,251uy); C3b(179uy,205uy,227uy); C3b(140uy,150uy,198uy); C3b(136uy,86uy,167uy) + C3b(129uy,15uy,124uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(237uy,248uy,251uy); C3b(191uy,211uy,230uy); C3b(158uy,188uy,218uy); C3b(140uy,150uy,198uy) + C3b(136uy,86uy,167uy); C3b(129uy,15uy,124uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(237uy,248uy,251uy); C3b(191uy,211uy,230uy); C3b(158uy,188uy,218uy); C3b(140uy,150uy,198uy) + C3b(140uy,107uy,177uy); C3b(136uy,65uy,157uy); C3b(110uy,1uy,107uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,252uy,253uy); C3b(224uy,236uy,244uy); C3b(191uy,211uy,230uy); C3b(158uy,188uy,218uy) + C3b(140uy,150uy,198uy); C3b(140uy,107uy,177uy); C3b(136uy,65uy,157uy); C3b(110uy,1uy,107uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,252uy,253uy); C3b(224uy,236uy,244uy); C3b(191uy,211uy,230uy); C3b(158uy,188uy,218uy) + C3b(140uy,150uy,198uy); C3b(140uy,107uy,177uy); C3b(136uy,65uy,157uy); C3b(129uy,15uy,124uy) + C3b(77uy,0uy,75uy) |] + } + + ] + } + + let Oranges = + { + Name = Sym.ofString "Oranges" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(254uy,230uy,206uy); C3b(253uy,174uy,107uy); C3b(230uy,85uy,13uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.LCD + Colors = [| C3b(254uy,237uy,222uy); C3b(253uy,190uy,133uy); C3b(253uy,141uy,60uy); C3b(217uy,71uy,1uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.LCD + Colors = [| C3b(254uy,237uy,222uy); C3b(253uy,190uy,133uy); C3b(253uy,141uy,60uy); C3b(230uy,85uy,13uy) + C3b(166uy,54uy,3uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(254uy,237uy,222uy); C3b(253uy,208uy,162uy); C3b(253uy,174uy,107uy); C3b(253uy,141uy,60uy) + C3b(230uy,85uy,13uy); C3b(166uy,54uy,3uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(254uy,237uy,222uy); C3b(253uy,208uy,162uy); C3b(253uy,174uy,107uy); C3b(253uy,141uy,60uy) + C3b(241uy,105uy,19uy); C3b(217uy,72uy,1uy); C3b(140uy,45uy,4uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,245uy,235uy); C3b(254uy,230uy,206uy); C3b(253uy,208uy,162uy); C3b(253uy,174uy,107uy) + C3b(253uy,141uy,60uy); C3b(241uy,105uy,19uy); C3b(217uy,72uy,1uy); C3b(140uy,45uy,4uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,245uy,235uy); C3b(254uy,230uy,206uy); C3b(253uy,208uy,162uy); C3b(253uy,174uy,107uy) + C3b(253uy,141uy,60uy); C3b(241uy,105uy,19uy); C3b(217uy,72uy,1uy); C3b(166uy,54uy,3uy) + C3b(127uy,39uy,4uy) |] + } + + ] + } + + let BuGn = + { + Name = Sym.ofString "BuGn" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(229uy,245uy,249uy); C3b(153uy,216uy,201uy); C3b(44uy,162uy,95uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print + Colors = [| C3b(237uy,248uy,251uy); C3b(178uy,226uy,226uy); C3b(102uy,194uy,164uy); C3b(35uy,139uy,69uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(237uy,248uy,251uy); C3b(178uy,226uy,226uy); C3b(102uy,194uy,164uy); C3b(44uy,162uy,95uy) + C3b(0uy,109uy,44uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(237uy,248uy,251uy); C3b(204uy,236uy,230uy); C3b(153uy,216uy,201uy); C3b(102uy,194uy,164uy) + C3b(44uy,162uy,95uy); C3b(0uy,109uy,44uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(237uy,248uy,251uy); C3b(204uy,236uy,230uy); C3b(153uy,216uy,201uy); C3b(102uy,194uy,164uy) + C3b(65uy,174uy,118uy); C3b(35uy,139uy,69uy); C3b(0uy,88uy,36uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,252uy,253uy); C3b(229uy,245uy,249uy); C3b(204uy,236uy,230uy); C3b(153uy,216uy,201uy) + C3b(102uy,194uy,164uy); C3b(65uy,174uy,118uy); C3b(35uy,139uy,69uy); C3b(0uy,88uy,36uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,252uy,253uy); C3b(229uy,245uy,249uy); C3b(204uy,236uy,230uy); C3b(153uy,216uy,201uy) + C3b(102uy,194uy,164uy); C3b(65uy,174uy,118uy); C3b(35uy,139uy,69uy); C3b(0uy,109uy,44uy) + C3b(0uy,68uy,27uy) |] + } + + ] + } + + let YlOrBr = + { + Name = Sym.ofString "YlOrBr" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(255uy,247uy,188uy); C3b(254uy,196uy,79uy); C3b(217uy,95uy,14uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print + Colors = [| C3b(255uy,255uy,212uy); C3b(254uy,217uy,142uy); C3b(254uy,153uy,41uy); C3b(204uy,76uy,2uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,212uy); C3b(254uy,217uy,142uy); C3b(254uy,153uy,41uy); C3b(217uy,95uy,14uy) + C3b(153uy,52uy,4uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,212uy); C3b(254uy,227uy,145uy); C3b(254uy,196uy,79uy); C3b(254uy,153uy,41uy) + C3b(217uy,95uy,14uy); C3b(153uy,52uy,4uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,212uy); C3b(254uy,227uy,145uy); C3b(254uy,196uy,79uy); C3b(254uy,153uy,41uy) + C3b(236uy,112uy,20uy); C3b(204uy,76uy,2uy); C3b(140uy,45uy,4uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,229uy); C3b(255uy,247uy,188uy); C3b(254uy,227uy,145uy); C3b(254uy,196uy,79uy) + C3b(254uy,153uy,41uy); C3b(236uy,112uy,20uy); C3b(204uy,76uy,2uy); C3b(140uy,45uy,4uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,229uy); C3b(255uy,247uy,188uy); C3b(254uy,227uy,145uy); C3b(254uy,196uy,79uy) + C3b(254uy,153uy,41uy); C3b(236uy,112uy,20uy); C3b(204uy,76uy,2uy); C3b(153uy,52uy,4uy) + C3b(102uy,37uy,6uy) |] + } + + ] + } + + let YlGn = + { + Name = Sym.ofString "YlGn" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(247uy,252uy,185uy); C3b(173uy,221uy,142uy); C3b(49uy,163uy,84uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(255uy,255uy,204uy); C3b(194uy,230uy,153uy); C3b(120uy,198uy,121uy); C3b(35uy,132uy,67uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(255uy,255uy,204uy); C3b(194uy,230uy,153uy); C3b(120uy,198uy,121uy); C3b(49uy,163uy,84uy) + C3b(0uy,104uy,55uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,204uy); C3b(217uy,240uy,163uy); C3b(173uy,221uy,142uy); C3b(120uy,198uy,121uy) + C3b(49uy,163uy,84uy); C3b(0uy,104uy,55uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,204uy); C3b(217uy,240uy,163uy); C3b(173uy,221uy,142uy); C3b(120uy,198uy,121uy) + C3b(65uy,171uy,93uy); C3b(35uy,132uy,67uy); C3b(0uy,90uy,50uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,229uy); C3b(247uy,252uy,185uy); C3b(217uy,240uy,163uy); C3b(173uy,221uy,142uy) + C3b(120uy,198uy,121uy); C3b(65uy,171uy,93uy); C3b(35uy,132uy,67uy); C3b(0uy,90uy,50uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,229uy); C3b(247uy,252uy,185uy); C3b(217uy,240uy,163uy); C3b(173uy,221uy,142uy) + C3b(120uy,198uy,121uy); C3b(65uy,171uy,93uy); C3b(35uy,132uy,67uy); C3b(0uy,104uy,55uy) + C3b(0uy,69uy,41uy) |] + } + + ] + } + + let Reds = + { + Name = Sym.ofString "Reds" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(254uy,224uy,210uy); C3b(252uy,146uy,114uy); C3b(222uy,45uy,38uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(254uy,229uy,217uy); C3b(252uy,174uy,145uy); C3b(251uy,106uy,74uy); C3b(203uy,24uy,29uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(254uy,229uy,217uy); C3b(252uy,174uy,145uy); C3b(251uy,106uy,74uy); C3b(222uy,45uy,38uy) + C3b(165uy,15uy,21uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(254uy,229uy,217uy); C3b(252uy,187uy,161uy); C3b(252uy,146uy,114uy); C3b(251uy,106uy,74uy) + C3b(222uy,45uy,38uy); C3b(165uy,15uy,21uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(254uy,229uy,217uy); C3b(252uy,187uy,161uy); C3b(252uy,146uy,114uy); C3b(251uy,106uy,74uy) + C3b(239uy,59uy,44uy); C3b(203uy,24uy,29uy); C3b(153uy,0uy,13uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,245uy,240uy); C3b(254uy,224uy,210uy); C3b(252uy,187uy,161uy); C3b(252uy,146uy,114uy) + C3b(251uy,106uy,74uy); C3b(239uy,59uy,44uy); C3b(203uy,24uy,29uy); C3b(153uy,0uy,13uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,245uy,240uy); C3b(254uy,224uy,210uy); C3b(252uy,187uy,161uy); C3b(252uy,146uy,114uy) + C3b(251uy,106uy,74uy); C3b(239uy,59uy,44uy); C3b(203uy,24uy,29uy); C3b(165uy,15uy,21uy) + C3b(103uy,0uy,13uy) |] + } + + ] + } + + let RdPu = + { + Name = Sym.ofString "RdPu" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(253uy,224uy,221uy); C3b(250uy,159uy,181uy); C3b(197uy,27uy,138uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(254uy,235uy,226uy); C3b(251uy,180uy,185uy); C3b(247uy,104uy,161uy); C3b(174uy,1uy,126uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(254uy,235uy,226uy); C3b(251uy,180uy,185uy); C3b(247uy,104uy,161uy); C3b(197uy,27uy,138uy) + C3b(122uy,1uy,119uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(254uy,235uy,226uy); C3b(252uy,197uy,192uy); C3b(250uy,159uy,181uy); C3b(247uy,104uy,161uy) + C3b(197uy,27uy,138uy); C3b(122uy,1uy,119uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(254uy,235uy,226uy); C3b(252uy,197uy,192uy); C3b(250uy,159uy,181uy); C3b(247uy,104uy,161uy) + C3b(221uy,52uy,151uy); C3b(174uy,1uy,126uy); C3b(122uy,1uy,119uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,247uy,243uy); C3b(253uy,224uy,221uy); C3b(252uy,197uy,192uy); C3b(250uy,159uy,181uy) + C3b(247uy,104uy,161uy); C3b(221uy,52uy,151uy); C3b(174uy,1uy,126uy); C3b(122uy,1uy,119uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,247uy,243uy); C3b(253uy,224uy,221uy); C3b(252uy,197uy,192uy); C3b(250uy,159uy,181uy) + C3b(247uy,104uy,161uy); C3b(221uy,52uy,151uy); C3b(174uy,1uy,126uy); C3b(122uy,1uy,119uy) + C3b(73uy,0uy,106uy) |] + } + + ] + } + + let Greens = + { + Name = Sym.ofString "Greens" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(229uy,245uy,224uy); C3b(161uy,217uy,155uy); C3b(49uy,163uy,84uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(237uy,248uy,233uy); C3b(186uy,228uy,179uy); C3b(116uy,196uy,118uy); C3b(35uy,139uy,69uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(237uy,248uy,233uy); C3b(186uy,228uy,179uy); C3b(116uy,196uy,118uy); C3b(49uy,163uy,84uy) + C3b(0uy,109uy,44uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(237uy,248uy,233uy); C3b(199uy,233uy,192uy); C3b(161uy,217uy,155uy); C3b(116uy,196uy,118uy) + C3b(49uy,163uy,84uy); C3b(0uy,109uy,44uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(237uy,248uy,233uy); C3b(199uy,233uy,192uy); C3b(161uy,217uy,155uy); C3b(116uy,196uy,118uy) + C3b(65uy,171uy,93uy); C3b(35uy,139uy,69uy); C3b(0uy,90uy,50uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,252uy,245uy); C3b(229uy,245uy,224uy); C3b(199uy,233uy,192uy); C3b(161uy,217uy,155uy) + C3b(116uy,196uy,118uy); C3b(65uy,171uy,93uy); C3b(35uy,139uy,69uy); C3b(0uy,90uy,50uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,252uy,245uy); C3b(229uy,245uy,224uy); C3b(199uy,233uy,192uy); C3b(161uy,217uy,155uy) + C3b(116uy,196uy,118uy); C3b(65uy,171uy,93uy); C3b(35uy,139uy,69uy); C3b(0uy,109uy,44uy) + C3b(0uy,68uy,27uy) |] + } + + ] + } + + let YlGnBu = + { + Name = Sym.ofString "YlGnBu" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(237uy,248uy,177uy); C3b(127uy,205uy,187uy); C3b(44uy,127uy,184uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(255uy,255uy,204uy); C3b(161uy,218uy,180uy); C3b(65uy,182uy,196uy); C3b(34uy,94uy,168uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print + Colors = [| C3b(255uy,255uy,204uy); C3b(161uy,218uy,180uy); C3b(65uy,182uy,196uy); C3b(44uy,127uy,184uy) + C3b(37uy,52uy,148uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,204uy); C3b(199uy,233uy,180uy); C3b(127uy,205uy,187uy); C3b(65uy,182uy,196uy) + C3b(44uy,127uy,184uy); C3b(37uy,52uy,148uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,204uy); C3b(199uy,233uy,180uy); C3b(127uy,205uy,187uy); C3b(65uy,182uy,196uy) + C3b(29uy,145uy,192uy); C3b(34uy,94uy,168uy); C3b(12uy,44uy,132uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,217uy); C3b(237uy,248uy,177uy); C3b(199uy,233uy,180uy); C3b(127uy,205uy,187uy) + C3b(65uy,182uy,196uy); C3b(29uy,145uy,192uy); C3b(34uy,94uy,168uy); C3b(12uy,44uy,132uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,217uy); C3b(237uy,248uy,177uy); C3b(199uy,233uy,180uy); C3b(127uy,205uy,187uy) + C3b(65uy,182uy,196uy); C3b(29uy,145uy,192uy); C3b(34uy,94uy,168uy); C3b(37uy,52uy,148uy) + C3b(8uy,29uy,88uy) |] + } + + ] + } + + let Purples = + { + Name = Sym.ofString "Purples" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(239uy,237uy,245uy); C3b(188uy,189uy,220uy); C3b(117uy,107uy,177uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(242uy,240uy,247uy); C3b(203uy,201uy,226uy); C3b(158uy,154uy,200uy); C3b(106uy,81uy,163uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(242uy,240uy,247uy); C3b(203uy,201uy,226uy); C3b(158uy,154uy,200uy); C3b(117uy,107uy,177uy) + C3b(84uy,39uy,143uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(242uy,240uy,247uy); C3b(218uy,218uy,235uy); C3b(188uy,189uy,220uy); C3b(158uy,154uy,200uy) + C3b(117uy,107uy,177uy); C3b(84uy,39uy,143uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(242uy,240uy,247uy); C3b(218uy,218uy,235uy); C3b(188uy,189uy,220uy); C3b(158uy,154uy,200uy) + C3b(128uy,125uy,186uy); C3b(106uy,81uy,163uy); C3b(74uy,20uy,134uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(252uy,251uy,253uy); C3b(239uy,237uy,245uy); C3b(218uy,218uy,235uy); C3b(188uy,189uy,220uy) + C3b(158uy,154uy,200uy); C3b(128uy,125uy,186uy); C3b(106uy,81uy,163uy); C3b(74uy,20uy,134uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(252uy,251uy,253uy); C3b(239uy,237uy,245uy); C3b(218uy,218uy,235uy); C3b(188uy,189uy,220uy) + C3b(158uy,154uy,200uy); C3b(128uy,125uy,186uy); C3b(106uy,81uy,163uy); C3b(84uy,39uy,143uy) + C3b(63uy,0uy,125uy) |] + } + + ] + } + + let GnBu = + { + Name = Sym.ofString "GnBu" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(224uy,243uy,219uy); C3b(168uy,221uy,181uy); C3b(67uy,162uy,202uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(240uy,249uy,232uy); C3b(186uy,228uy,188uy); C3b(123uy,204uy,196uy); C3b(43uy,140uy,190uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print + Colors = [| C3b(240uy,249uy,232uy); C3b(186uy,228uy,188uy); C3b(123uy,204uy,196uy); C3b(67uy,162uy,202uy) + C3b(8uy,104uy,172uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(240uy,249uy,232uy); C3b(204uy,235uy,197uy); C3b(168uy,221uy,181uy); C3b(123uy,204uy,196uy) + C3b(67uy,162uy,202uy); C3b(8uy,104uy,172uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(240uy,249uy,232uy); C3b(204uy,235uy,197uy); C3b(168uy,221uy,181uy); C3b(123uy,204uy,196uy) + C3b(78uy,179uy,211uy); C3b(43uy,140uy,190uy); C3b(8uy,88uy,158uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,252uy,240uy); C3b(224uy,243uy,219uy); C3b(204uy,235uy,197uy); C3b(168uy,221uy,181uy) + C3b(123uy,204uy,196uy); C3b(78uy,179uy,211uy); C3b(43uy,140uy,190uy); C3b(8uy,88uy,158uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,252uy,240uy); C3b(224uy,243uy,219uy); C3b(204uy,235uy,197uy); C3b(168uy,221uy,181uy) + C3b(123uy,204uy,196uy); C3b(78uy,179uy,211uy); C3b(43uy,140uy,190uy); C3b(8uy,104uy,172uy) + C3b(8uy,64uy,129uy) |] + } + + ] + } + + let Greys = + { + Name = Sym.ofString "Greys" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(240uy,240uy,240uy); C3b(189uy,189uy,189uy); C3b(99uy,99uy,99uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print + Colors = [| C3b(247uy,247uy,247uy); C3b(204uy,204uy,204uy); C3b(150uy,150uy,150uy); C3b(82uy,82uy,82uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,247uy,247uy); C3b(204uy,204uy,204uy); C3b(150uy,150uy,150uy); C3b(99uy,99uy,99uy) + C3b(37uy,37uy,37uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,247uy,247uy); C3b(217uy,217uy,217uy); C3b(189uy,189uy,189uy); C3b(150uy,150uy,150uy) + C3b(99uy,99uy,99uy); C3b(37uy,37uy,37uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,247uy,247uy); C3b(217uy,217uy,217uy); C3b(189uy,189uy,189uy); C3b(150uy,150uy,150uy) + C3b(115uy,115uy,115uy); C3b(82uy,82uy,82uy); C3b(37uy,37uy,37uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,255uy); C3b(240uy,240uy,240uy); C3b(217uy,217uy,217uy); C3b(189uy,189uy,189uy) + C3b(150uy,150uy,150uy); C3b(115uy,115uy,115uy); C3b(82uy,82uy,82uy); C3b(37uy,37uy,37uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,255uy); C3b(240uy,240uy,240uy); C3b(217uy,217uy,217uy); C3b(189uy,189uy,189uy) + C3b(150uy,150uy,150uy); C3b(115uy,115uy,115uy); C3b(82uy,82uy,82uy); C3b(37uy,37uy,37uy) + C3b(0uy,0uy,0uy) |] + } + + ] + } + + let YlOrRd = + { + Name = Sym.ofString "YlOrRd" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(255uy,237uy,160uy); C3b(254uy,178uy,76uy); C3b(240uy,59uy,32uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print + Colors = [| C3b(255uy,255uy,178uy); C3b(254uy,204uy,92uy); C3b(253uy,141uy,60uy); C3b(227uy,26uy,28uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,178uy); C3b(254uy,204uy,92uy); C3b(253uy,141uy,60uy); C3b(240uy,59uy,32uy) + C3b(189uy,0uy,38uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,178uy); C3b(254uy,217uy,118uy); C3b(254uy,178uy,76uy); C3b(253uy,141uy,60uy) + C3b(240uy,59uy,32uy); C3b(189uy,0uy,38uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,178uy); C3b(254uy,217uy,118uy); C3b(254uy,178uy,76uy); C3b(253uy,141uy,60uy) + C3b(252uy,78uy,42uy); C3b(227uy,26uy,28uy); C3b(177uy,0uy,38uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,204uy); C3b(255uy,237uy,160uy); C3b(254uy,217uy,118uy); C3b(254uy,178uy,76uy) + C3b(253uy,141uy,60uy); C3b(252uy,78uy,42uy); C3b(227uy,26uy,28uy); C3b(177uy,0uy,38uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,255uy,204uy); C3b(255uy,237uy,160uy); C3b(254uy,217uy,118uy); C3b(254uy,178uy,76uy) + C3b(253uy,141uy,60uy); C3b(252uy,78uy,42uy); C3b(227uy,26uy,28uy); C3b(189uy,0uy,38uy) + C3b(128uy,0uy,38uy) |] + } + + ] + } + + let PuRd = + { + Name = Sym.ofString "PuRd" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(231uy,225uy,239uy); C3b(201uy,148uy,199uy); C3b(221uy,28uy,119uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(241uy,238uy,246uy); C3b(215uy,181uy,216uy); C3b(223uy,101uy,176uy); C3b(206uy,18uy,86uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD + Colors = [| C3b(241uy,238uy,246uy); C3b(215uy,181uy,216uy); C3b(223uy,101uy,176uy); C3b(221uy,28uy,119uy) + C3b(152uy,0uy,67uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(241uy,238uy,246uy); C3b(212uy,185uy,218uy); C3b(201uy,148uy,199uy); C3b(223uy,101uy,176uy) + C3b(221uy,28uy,119uy); C3b(152uy,0uy,67uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(241uy,238uy,246uy); C3b(212uy,185uy,218uy); C3b(201uy,148uy,199uy); C3b(223uy,101uy,176uy) + C3b(231uy,41uy,138uy); C3b(206uy,18uy,86uy); C3b(145uy,0uy,63uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,244uy,249uy); C3b(231uy,225uy,239uy); C3b(212uy,185uy,218uy); C3b(201uy,148uy,199uy) + C3b(223uy,101uy,176uy); C3b(231uy,41uy,138uy); C3b(206uy,18uy,86uy); C3b(145uy,0uy,63uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,244uy,249uy); C3b(231uy,225uy,239uy); C3b(212uy,185uy,218uy); C3b(201uy,148uy,199uy) + C3b(223uy,101uy,176uy); C3b(231uy,41uy,138uy); C3b(206uy,18uy,86uy); C3b(152uy,0uy,67uy) + C3b(103uy,0uy,31uy) |] + } + + ] + } + + let Blues = + { + Name = Sym.ofString "Blues" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(222uy,235uy,247uy); C3b(158uy,202uy,225uy); C3b(49uy,130uy,189uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(239uy,243uy,255uy); C3b(189uy,215uy,231uy); C3b(107uy,174uy,214uy); C3b(33uy,113uy,181uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(239uy,243uy,255uy); C3b(189uy,215uy,231uy); C3b(107uy,174uy,214uy); C3b(49uy,130uy,189uy) + C3b(8uy,81uy,156uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(239uy,243uy,255uy); C3b(198uy,219uy,239uy); C3b(158uy,202uy,225uy); C3b(107uy,174uy,214uy) + C3b(49uy,130uy,189uy); C3b(8uy,81uy,156uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(239uy,243uy,255uy); C3b(198uy,219uy,239uy); C3b(158uy,202uy,225uy); C3b(107uy,174uy,214uy) + C3b(66uy,146uy,198uy); C3b(33uy,113uy,181uy); C3b(8uy,69uy,148uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,251uy,255uy); C3b(222uy,235uy,247uy); C3b(198uy,219uy,239uy); C3b(158uy,202uy,225uy) + C3b(107uy,174uy,214uy); C3b(66uy,146uy,198uy); C3b(33uy,113uy,181uy); C3b(8uy,69uy,148uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(247uy,251uy,255uy); C3b(222uy,235uy,247uy); C3b(198uy,219uy,239uy); C3b(158uy,202uy,225uy) + C3b(107uy,174uy,214uy); C3b(66uy,146uy,198uy); C3b(33uy,113uy,181uy); C3b(8uy,81uy,156uy) + C3b(8uy,48uy,107uy) |] + } + + ] + } + + let PuBuGn = + { + Name = Sym.ofString "PuBuGn" + Type = SchemeType.Sequential + Palettes = + MapExt.ofList [ + 3, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.Print ||| PaletteUsage.LCD ||| PaletteUsage.PhotoCopy + Colors = [| C3b(236uy,226uy,240uy); C3b(166uy,189uy,219uy); C3b(28uy,144uy,153uy) |] + } + + 4, { + Usage = PaletteUsage.ColorBlind ||| PaletteUsage.LCD + Colors = [| C3b(246uy,239uy,247uy); C3b(189uy,201uy,225uy); C3b(103uy,169uy,207uy); C3b(2uy,129uy,138uy) |] + } + + 5, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(246uy,239uy,247uy); C3b(189uy,201uy,225uy); C3b(103uy,169uy,207uy); C3b(28uy,144uy,153uy) + C3b(1uy,108uy,89uy) |] + } + + 6, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(246uy,239uy,247uy); C3b(208uy,209uy,230uy); C3b(166uy,189uy,219uy); C3b(103uy,169uy,207uy) + C3b(28uy,144uy,153uy); C3b(1uy,108uy,89uy) |] + } + + 7, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(246uy,239uy,247uy); C3b(208uy,209uy,230uy); C3b(166uy,189uy,219uy); C3b(103uy,169uy,207uy) + C3b(54uy,144uy,192uy); C3b(2uy,129uy,138uy); C3b(1uy,100uy,80uy) |] + } + + 8, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,247uy,251uy); C3b(236uy,226uy,240uy); C3b(208uy,209uy,230uy); C3b(166uy,189uy,219uy) + C3b(103uy,169uy,207uy); C3b(54uy,144uy,192uy); C3b(2uy,129uy,138uy); C3b(1uy,100uy,80uy) |] + } + + 9, { + Usage = PaletteUsage.ColorBlind + Colors = [| C3b(255uy,247uy,251uy); C3b(236uy,226uy,240uy); C3b(208uy,209uy,230uy); C3b(166uy,189uy,219uy) + C3b(103uy,169uy,207uy); C3b(54uy,144uy,192uy); C3b(2uy,129uy,138uy); C3b(1uy,108uy,89uy) + C3b(1uy,70uy,54uy) |] + } + + ] + } + + /// Array of all available color schemes. + let All = + [| + Diverging.Spectral; Diverging.RdYlGn; Diverging.RdBu; Diverging.PiYG; Diverging.PRGn; Diverging.RdYlBu + Diverging.BrBG; Diverging.RdGy; Diverging.PuOr; Qualitative.Set2; Qualitative.Accent; Qualitative.Set1 + Qualitative.Set3; Qualitative.Dark2; Qualitative.Paired; Qualitative.Pastel2; Qualitative.Pastel1; Sequential.OrRd + Sequential.PuBu; Sequential.BuPu; Sequential.Oranges; Sequential.BuGn; Sequential.YlOrBr; Sequential.YlGn + Sequential.Reds; Sequential.RdPu; Sequential.Greens; Sequential.YlGnBu; Sequential.Purples; Sequential.GnBu + Sequential.Greys; Sequential.YlOrRd; Sequential.PuRd; Sequential.Blues; Sequential.PuBuGn + |] diff --git a/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fsx b/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fsx new file mode 100644 index 00000000..313d4ef6 --- /dev/null +++ b/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fsx @@ -0,0 +1,247 @@ +#r "../../../bin/Debug/netstandard2.0/Aardvark.Base.dll" +#r "../../../bin/Debug/netstandard2.0/Aardvark.Base.FSharp.dll" +#r "../../../bin/Debug/net6.0/FSharp.Data.Adaptive.dll" +#r "System.Net.Http.dll" + +open Aardvark.Base +open System +open System.IO +open System.Text +open System.Text.RegularExpressions +open System.Net.Http + +fsi.ShowDeclarationValues <- false + +let source = + let ws = Regex(@"\s+", RegexOptions.Compiled) + + use client = new HttpClient() + use task = client.GetStringAsync "https://raw.githubusercontent.com/axismaps/colorbrewer/master/colorbrewer_schemes.js" + task.Wait() + + task.Result + |> String.getLines + |> Array.map (fun str -> ws.Replace(str, "")) + +module Regex = + + let Scheme = + Regex( + @"^(?[A-Za-z0-9]+):\{" + + @"(?(?:[0-9]:\[.+?\],?)+)" + + @"'properties':\{(?.+)\}\},?$", + RegexOptions.Multiline + ); + + let Palette = + Regex(@"(?[0-9]+):\[(?[0-9'rgb\(\),]+)\]", RegexOptions.Compiled); + + let Color = + Regex(@"'(?rgb\([0-9]{1,3},[0-9]{1,3},[0-9]{1,3}\))'", RegexOptions.Compiled) + + let RGB = + Regex(@"^rgb\((?[0-9]{1,3}),(?[0-9]{1,3}),(?[0-9]{1,3})\)$", RegexOptions.Compiled) + + let Property = + Regex(@"'(?[a-zA-Z]+)':(?:'(?[a-zA-Z]+)'|\[(?[0-9,]+)\])", RegexOptions.Compiled); + + +type Severity = + | Warning = 0 + | Error = 1 + +let messages = ResizeArray() + +let log severity fmt = + Printf.kprintf (fun str -> messages.Add (severity, str)) fmt + +let schemes = + source |> Array.choose (fun str -> + let m = Regex.Scheme.Match str + + if m.Success then + let schemeName = m.Groups.["Name"].Value + + try + let properties = + Regex.Property.Matches m.Groups.["Properties"].Value + |> Seq.map (fun p -> + let value = + if p.Groups.["Value"].Success then p.Groups.["Value"].Value + else p.Groups.["PerPaletteValue"].Value + + p.Groups.["Key"].Value, value + ) + |> Map.ofSeq + + let typ = + match properties.["type"] with + | "div" -> ColorBrewer.SchemeType.Diverging + | "qual" -> ColorBrewer.SchemeType.Qualitative + | "seq" -> ColorBrewer.SchemeType.Sequential + | t -> failwithf "Unknown scheme type '%s'" t + + let colors = + Regex.Palette.Matches m.Groups.["Palettes"].Value + |> Seq.map (fun p -> + let size = Int32.Parse p.Groups.["Size"].Value + + let colors = + Regex.Color.Matches p.Groups.["Colors"].Value + |> Seq.map (fun m -> + let m = Regex.RGB.Match m.Groups.["Color"].Value + let r = m.Groups.["R"].Value + let g = m.Groups.["G"].Value + let b = m.Groups.["B"].Value + $"C3b({r}uy,{g}uy,{b}uy)" + ) + |> Array.ofSeq + + if colors.Length <> size then + failwithf "Size mismatch (expected %d but got %d)" size colors.Length + + colors + ) + |> Array.ofSeq + + let getFlags (propertyName : string) = + let values = + properties.[propertyName] |> String.split "," |> Array.map (fun str -> + Int32.Parse str = 1 + ) + + if values.Length >= colors.Length then values + elif values.Length = 1 then Array.replicate colors.Length values.[0] + else + let missing = Array.replicate (colors.Length - values.Length) false + log Severity.Warning "Property '%s' in scheme '%s' has %d entries but expected %d." propertyName schemeName values.Length colors.Length + Array.concat [values; missing] + + let blind = getFlags "blind" + let print = getFlags "print" + let copy = getFlags "copy" + let screen = getFlags "screen" + + let palettes = + colors |> Array.mapi (fun i c -> + let mutable usage = Set.empty + if blind.[i] then usage <- usage |> Set.add ColorBrewer.PaletteUsage.ColorBlind + if print.[i] then usage <- usage |> Set.add ColorBrewer.PaletteUsage.Print + if copy.[i] then usage <- usage |> Set.add ColorBrewer.PaletteUsage.PhotoCopy + if screen.[i] then usage <- usage |> Set.add ColorBrewer.PaletteUsage.LCD + + c.Length, {| Colors = c; Usage = usage |} + ) + |> Map.ofArray + + Some {| Name = schemeName; Type = typ; Palettes = palettes |} + + with e -> + log Severity.Error "Failed to parse scheme %s: %s" schemeName e.Message + None + else + None + ) + +printfn "Found %d schemes" schemes.Length + +let writeToFile() = + let builder = StringBuilder() + + let writeln indent fmt = + Printf.kprintf (fun str -> builder.AppendLine(str |> String.indent indent) |> ignore) fmt + + let schemeTypeComments = + Map.ofList [ + ColorBrewer.SchemeType.Diverging, "/// Diverging schemes put equal emphasis on mid-range critical values and extremes at both ends of the data range.\n/// The critical class or break in the middle of the legend is emphasized with light colors and low and high extremes are\n/// emphasized with dark colors that have contrasting hues." + + ColorBrewer.SchemeType.Qualitative, "/// Qualitative schemes do not imply magnitude differences between legend classes, and hues are used to\n/// create the primary visual differences between classes. Qualitative schemes are best suited to representing nominal or categorical data." + + ColorBrewer.SchemeType.Sequential, "/// Sequential schemes are suited to ordered data that progress from low to high.\n/// Lightness steps dominate the look of these schemes, with light colors for low data values to dark colors for high data values." + ] + + writeln 0 "namespace Aardvark.Base" + writeln 0 "" + writeln 0 "[]" + writeln 0 "module ColorBrewerSchemes =" + writeln 1 "open ColorBrewer" + writeln 0 "" + writeln 1 "/// Brewer color schemes designed for chloropleth map visualizations." + writeln 1 "module ColorBrewer =" + writeln 0 "" + writeln 2 "module Scheme =" + writeln 0 "" + + let grouped = + schemes |> Array.groupBy (fun s -> s.Type) + + for (typ, schemes) in grouped do + + for c in schemeTypeComments.[typ] |> String.getLines do + writeln 3 "%s" c + + writeln 3 "module %A =" typ + writeln 0 "" + + for s in schemes do + writeln 4 "let %s =" s.Name + writeln 5 "{" + writeln 6 "Name = Sym.ofString \"%s\"" s.Name + writeln 6 "Type = SchemeType.%A" s.Type + writeln 6 "Palettes =" + writeln 7 "MapExt.ofList [" + + for (KeyValue(n, p)) in s.Palettes do + let usage = + if Set.isEmpty p.Usage then "PaletteUsage.None" + else p.Usage |> Seq.map (fun u -> $"PaletteUsage.{u}") |> String.concat " ||| " + + let colors = + Array.chunkBySize 4 p.Colors + |> Array.map (String.concat "; ") + + colors.[0] <- $"Colors = [| {colors.[0]}" + colors.[colors.Length - 1] <- $"{colors.[colors.Length - 1]} |]" + + writeln 8 "%d, {" n + writeln 9 "Usage = %s" usage + + writeln 9 "%s" colors.[0] + for i = 1 to colors.Length - 1 do + writeln 12 "%s" colors.[i] + + writeln 8 "}" + writeln 0 "" + + writeln 7 "]" + writeln 5 "}" + writeln 0 "" + + writeln 3 "/// Array of all available color schemes." + writeln 3 "let All =" + writeln 4 "[|" + + let all = + grouped |> Array.collect (fun (typ, schemes) -> + schemes |> Array.map (fun s -> $"{typ}.{s.Name}") + ) + |> Array.chunkBySize 6 + |> Array.map (String.concat "; ") + + for schemes in all do + writeln 5 "%s" schemes + + writeln 4 "|]" + + + let file = Path.Combine(__SOURCE_DIRECTORY__, "ColorBrewerSchemes.fs") + File.writeAllText file (builder.ToString()) + + for s in schemes do + printfn "%s" s.Name + printfn "%A" s.Palettes + + for (s, e) in messages do + printfn "[%A] %s" s e + +writeToFile() \ No newline at end of file From a397eee2cd1b7e392f2fae147d99894a04e29e98 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 17:17:41 +0100 Subject: [PATCH 25/45] [FSharp] Add String.replace --- src/Aardvark.Base.FSharp/Utilities/Interop/String.fs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Aardvark.Base.FSharp/Utilities/Interop/String.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/String.fs index 6e7b1413..0ab294b1 100644 --- a/src/Aardvark.Base.FSharp/Utilities/Interop/String.fs +++ b/src/Aardvark.Base.FSharp/Utilities/Interop/String.fs @@ -111,4 +111,8 @@ module ``String Extensions`` = /// Removes all leading and trailing white-space characters from the string. let inline trim (str : string) = - str.Trim() \ No newline at end of file + str.Trim() + + /// Replaces all occurrences of the pattern with the replacement string. + let inline replace (pattern : string) (replacement : string) (str : string) = + str.Replace(pattern, replacement) \ No newline at end of file From d8cf8b9bf46bbf3de2b0ac3603263b33a05a0426 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 17:20:20 +0100 Subject: [PATCH 26/45] Update RELEASE_NOTES.md --- RELEASE_NOTES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index ff75f248..cc9c20e1 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,8 @@ +### 5.2.28 +* Added Brewer color schemes +* Added String.replace +* Added IDictionary.TryPop overload with output argument + ### 5.2.27 * Improved and fixed RangeSet implementation * Added utilities for ValueOption From 59ac6436420834b245b9a9fb05ab1f18c6c1f5fa Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Oct 2023 19:12:25 +0100 Subject: [PATCH 27/45] Fix typo --- src/Aardvark.Base.FSharp/Color/ColorBrewer.fs | 2 +- src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fs | 2 +- src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Aardvark.Base.FSharp/Color/ColorBrewer.fs b/src/Aardvark.Base.FSharp/Color/ColorBrewer.fs index 41b7324d..d0abca79 100644 --- a/src/Aardvark.Base.FSharp/Color/ColorBrewer.fs +++ b/src/Aardvark.Base.FSharp/Color/ColorBrewer.fs @@ -4,7 +4,7 @@ open System open System.Collections open System.Collections.Generic -/// Brewer color schemes designed for chloropleth map visualizations. +/// Brewer color schemes designed for choropleth map visualizations. module ColorBrewer = [] diff --git a/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fs b/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fs index c2c9bca8..2a22cc68 100644 --- a/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fs +++ b/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fs @@ -4,7 +4,7 @@ namespace Aardvark.Base module ColorBrewerSchemes = open ColorBrewer - /// Brewer color schemes designed for chloropleth map visualizations. + /// Brewer color schemes designed for choropleth map visualizations. module ColorBrewer = module Scheme = diff --git a/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fsx b/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fsx index 313d4ef6..85fbbe0a 100644 --- a/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fsx +++ b/src/Aardvark.Base.FSharp/Color/ColorBrewerSchemes.fsx @@ -166,7 +166,7 @@ let writeToFile() = writeln 0 "module ColorBrewerSchemes =" writeln 1 "open ColorBrewer" writeln 0 "" - writeln 1 "/// Brewer color schemes designed for chloropleth map visualizations." + writeln 1 "/// Brewer color schemes designed for choropleth map visualizations." writeln 1 "module ColorBrewer =" writeln 0 "" writeln 2 "module Scheme =" From a0a581a29955dd017055d0f1b2d45055216cf0e2 Mon Sep 17 00:00:00 2001 From: Attila Szabo Date: Thu, 21 Dec 2023 13:36:27 +0100 Subject: [PATCH 28/45] Update README.md --- README.md | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 23df751e..8840f739 100644 --- a/README.md +++ b/README.md @@ -2,35 +2,13 @@ ![MacOS](https://github.com/aardvark-platform/aardvark.base/workflows/MacOS/badge.svg) ![Linux](https://github.com/aardvark-platform/aardvark.base/workflows/Linux/badge.svg) - [![Discord](https://badgen.net/discord/online-members/UyecnhM)](https://discord.gg/UyecnhM) [![license](https://img.shields.io/github/license/aardvark-platform/aardvark.base.svg)](https://github.com/aardvark-platform/aardvark.base/blob/master/LICENSE) [The Aardvark Platform](https://aardvarkians.com/) | -[Platform Wiki](https://github.com/aardvarkplatform/aardvark.docs/wiki) | -[The Platform Walkthrough Repository](https://github.com/aardvark-platform/walkthrough) | -[Gallery](https://github.com/aardvarkplatform/aardvark.docs/wiki/Gallery) | -[Quickstart](https://github.com/aardvarkplatform/aardvark.docs/wiki/Quickstart-Windows) | -[Status](https://github.com/aardvarkplatform/aardvark.docs/wiki/Status) - - -Aardvark.Base consists of multiple platform-independent packages (netstandard2.0) delivering essential tools for visual computing, such as vectors and matrices, as well as many algorithms and data structures. -It is the lowest-level foundation of the open-source [Aardvark Platform](https://github.com/aardvark-platform/aardvark.docs/wiki) for visual computing, real-time graphics and visualization: - -repository | description -:-- | --- | -`aardvark.media` | a unified ELM-style UI framework for both 2D and 3D | -`aardvark.rendering` | powerful incremental rendering engine | -`aardvark.base` | math, geometry, algorithms, data structures | - -The repository `aardvark.base` includes many packages, e.g. - - [Aardvark.Base](https://www.nuget.org/packages/Aardvark.Base/): matrices, vectors, geometry, basic algorithms and data structures. - - [Aardvark.Base.FSharp](https://www.nuget.org/packages/Aardvark.Base.FSharp/): stuff you always need, optimized persistent (e.g. hash maps), ephemeral data structures (e.g. SkipList) as well as spatial data structures (e.g. bounding volume hierarchies). The package also contains an attribute grammar system exposed as an embedded domain specific language. We use it in [aardvark.rendering](https://github.com/aardvark-platform/aardvark.base) for our scene graph system, as described in [Attribute Grammars for Incremental Scene Graph Rendering](https://www.vrvis.at/publications/pdfs/PB-VRVis-2019-004.pdf). - - [Aardvark.Base.Incremental](https://www.nuget.org/packages/Aardvark.Base.Incremental/): incremental data structures similarly but extended to Hammer et al.'s paper [Adapton: Composable, Demand-Driven Incremental Computation](https://www.cs.umd.edu/~hammer/adapton/). Additionally to modifiable cells, we have more sophisticated optimized incremental data structures such as adaptive sets, maps etc. and computation expression builders to conveniently work with. - - [Aardvark.Base.Runtime](https://www.nuget.org/packages/Aardvark.Base.Runtime/): Crazy tools such as an AMD64 assembler used for incremental Just In Time Compilation as used in [aardvark.rendering](https://github.com/aardvark-platform/aardvark.base) - - [Aardvark.Data.Vrml97](https://www.nuget.org/packages/Aardvark.Data.Vrml97/): legacy Vrml97 parser - - [Aardvark.Geometry](https://www.nuget.org/packages/Aardvark.Geometry/): currently a rather small set of F# geometry tools. Most functionality regarding geometry lives in base and [algodat](https://github.com/aardvark-platform/aardvark.algodat) +[Gallery](https://github.com/aardvark-platform/aardvark.docs/wiki/Gallery) | +[Packages&Repositories](https://github.com/aardvark-platform/aardvark.docs/wiki/Packages-and-Repositories) -All packages are distributed under the [Apache 2.0 license](https://github.com/aardvark-platform/aardvark.base/blob/master/LICENSE). +Aardvark.Base is the foundation of the open-source [Aardvark Platform](https://github.com/aardvark-platform) for visual computing, real-time graphics, and visualization. It consists of multiple platform-independent packages (netstandard2.0) that deliver essential tools for visual computing, including vectors, matrices, algorithms, and data structures. -For support please have a look at [Aardvarkians](https://aardvarkians.com). +You can find demos and code in the Gallery and Packages&Repositories links above. For more information, please refer to the [aardvark.docs wiki](https://github.com/aardvark-platform/aardvark.docs/wiki). From 4f62cd5bab86a58a6a12319adff23420e267c4fe Mon Sep 17 00:00:00 2001 From: Attila Szabo Date: Fri, 22 Dec 2023 18:24:45 +0100 Subject: [PATCH 29/45] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8840f739..0dccc386 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,4 @@ Aardvark.Base is the foundation of the open-source [Aardvark Platform](https://github.com/aardvark-platform) for visual computing, real-time graphics, and visualization. It consists of multiple platform-independent packages (netstandard2.0) that deliver essential tools for visual computing, including vectors, matrices, algorithms, and data structures. -You can find demos and code in the Gallery and Packages&Repositories links above. For more information, please refer to the [aardvark.docs wiki](https://github.com/aardvark-platform/aardvark.docs/wiki). +You can find demos and code in the Gallery and Packages&Repositories links above. [This repository's wiki](https://github.com/aardvark-platform/aardvark.base/wiki) hosts technical documentation. For more information, please refer to the [aardvark.docs wiki](https://github.com/aardvark-platform/aardvark.docs/wiki). From d3291292fe6330248e5b5645897b2ae61ba0ffb5 Mon Sep 17 00:00:00 2001 From: Attila Szabo Date: Mon, 25 Dec 2023 17:35:09 +0100 Subject: [PATCH 30/45] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0dccc386..7e741144 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,6 @@ [Gallery](https://github.com/aardvark-platform/aardvark.docs/wiki/Gallery) | [Packages&Repositories](https://github.com/aardvark-platform/aardvark.docs/wiki/Packages-and-Repositories) -Aardvark.Base is the foundation of the open-source [Aardvark Platform](https://github.com/aardvark-platform) for visual computing, real-time graphics, and visualization. It consists of multiple platform-independent packages (netstandard2.0) that deliver essential tools for visual computing, including vectors, matrices, algorithms, and data structures. +Aardvark.Base is the foundation of the open-source [Aardvark Platform](https://github.com/aardvark-platform) for visual computing, real-time graphics, and visualization. It consists of multiple platform-independent packages (netstandard2.0) that deliver essential tools for visual computing, including vectors, matrices, algorithms, and data structures. Supported platforms are windows, linux, macOS. You can find demos and code in the Gallery and Packages&Repositories links above. [This repository's wiki](https://github.com/aardvark-platform/aardvark.base/wiki) hosts technical documentation. For more information, please refer to the [aardvark.docs wiki](https://github.com/aardvark-platform/aardvark.docs/wiki). From 0ccc0eec6f709a5ee33b5315d4c62e2a5e24d590 Mon Sep 17 00:00:00 2001 From: Attila Szabo Date: Mon, 25 Dec 2023 17:35:40 +0100 Subject: [PATCH 31/45] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e741144..b332a31b 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,6 @@ [Gallery](https://github.com/aardvark-platform/aardvark.docs/wiki/Gallery) | [Packages&Repositories](https://github.com/aardvark-platform/aardvark.docs/wiki/Packages-and-Repositories) -Aardvark.Base is the foundation of the open-source [Aardvark Platform](https://github.com/aardvark-platform) for visual computing, real-time graphics, and visualization. It consists of multiple platform-independent packages (netstandard2.0) that deliver essential tools for visual computing, including vectors, matrices, algorithms, and data structures. Supported platforms are windows, linux, macOS. +Aardvark.Base is the foundation of the open-source [Aardvark Platform](https://github.com/aardvark-platform) for visual computing, real-time graphics, and visualization. It consists of multiple platform-independent packages (netstandard2.0) that deliver essential tools for visual computing, including vectors, matrices, algorithms, data structures, or image loaders. Supported platforms are windows, linux, macOS. You can find demos and code in the Gallery and Packages&Repositories links above. [This repository's wiki](https://github.com/aardvark-platform/aardvark.base/wiki) hosts technical documentation. For more information, please refer to the [aardvark.docs wiki](https://github.com/aardvark-platform/aardvark.docs/wiki). From ed8a39c9581753de22e424b1ec9c8eb44225c9ad Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 6 Feb 2024 11:27:34 +0100 Subject: [PATCH 32/45] Fix color parsing to be independent of current culture See: https://github.com/aardvark-platform/aardvark.base/commit/62889e7324e3a88969ba490625e19a7cdb2ff923 --- src/Aardvark.Base/Math/Colors/Color_auto.cs | 20 +++++++++---------- .../Math/Colors/Color_template.cs | 3 ++- .../Aardvark.Base.Tests/Math/ColorTests.cs | 17 ++++++++++++---- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/Aardvark.Base/Math/Colors/Color_auto.cs b/src/Aardvark.Base/Math/Colors/Color_auto.cs index 5ce6fdb1..fbe75caa 100644 --- a/src/Aardvark.Base/Math/Colors/Color_auto.cs +++ b/src/Aardvark.Base/Math/Colors/Color_auto.cs @@ -1619,7 +1619,7 @@ public static bool TryParse(Text t, out C3b result) byte parse(Text t) { - if (!byte.TryParse(t.ToString(), out byte value)) + if (!byte.TryParse(t.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out byte value)) success = false; return value; @@ -3894,7 +3894,7 @@ public static bool TryParse(Text t, out C3us result) ushort parse(Text t) { - if (!ushort.TryParse(t.ToString(), out ushort value)) + if (!ushort.TryParse(t.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out ushort value)) success = false; return value; @@ -6092,7 +6092,7 @@ public static bool TryParse(Text t, out C3ui result) uint parse(Text t) { - if (!uint.TryParse(t.ToString(), out uint value)) + if (!uint.TryParse(t.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out uint value)) success = false; return value; @@ -8165,7 +8165,7 @@ public static bool TryParse(Text t, out C3f result) float parse(Text t) { - if (!float.TryParse(t.ToString(), out float value)) + if (!float.TryParse(t.ToString(), NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out float value)) success = false; return value; @@ -10275,7 +10275,7 @@ public static bool TryParse(Text t, out C3d result) double parse(Text t) { - if (!double.TryParse(t.ToString(), out double value)) + if (!double.TryParse(t.ToString(), NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double value)) success = false; return value; @@ -12905,7 +12905,7 @@ public static bool TryParse(Text t, out C4b result) byte parse(Text t) { - if (!byte.TryParse(t.ToString(), out byte value)) + if (!byte.TryParse(t.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out byte value)) success = false; return value; @@ -15464,7 +15464,7 @@ public static bool TryParse(Text t, out C4us result) ushort parse(Text t) { - if (!ushort.TryParse(t.ToString(), out ushort value)) + if (!ushort.TryParse(t.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out ushort value)) success = false; return value; @@ -17928,7 +17928,7 @@ public static bool TryParse(Text t, out C4ui result) uint parse(Text t) { - if (!uint.TryParse(t.ToString(), out uint value)) + if (!uint.TryParse(t.ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture, out uint value)) success = false; return value; @@ -20182,7 +20182,7 @@ public static bool TryParse(Text t, out C4f result) float parse(Text t) { - if (!float.TryParse(t.ToString(), out float value)) + if (!float.TryParse(t.ToString(), NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out float value)) success = false; return value; @@ -22476,7 +22476,7 @@ public static bool TryParse(Text t, out C4d result) double parse(Text t) { - if (!double.TryParse(t.ToString(), out double value)) + if (!double.TryParse(t.ToString(), NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out double value)) success = false; return value; diff --git a/src/Aardvark.Base/Math/Colors/Color_template.cs b/src/Aardvark.Base/Math/Colors/Color_template.cs index 8d7182b0..b36e2eae 100644 --- a/src/Aardvark.Base/Math/Colors/Color_template.cs +++ b/src/Aardvark.Base/Math/Colors/Color_template.cs @@ -259,6 +259,7 @@ namespace Aardvark.Base //# var getptr = "&" + ((ft == Meta.ByteType) ? fields[2] : fields[0]); //# var rgba = t.HasAlpha ? "RGBA" : "RGB"; //# var maxval = maxvalmap[ft]; + //# var parseNumberStyle = isReal ? "NumberStyles.Float | NumberStyles.AllowThousands" : "NumberStyles.Integer"; #region __type__ /// @@ -1165,7 +1166,7 @@ public static bool TryParse(Text t, out __type__ result) __ftype__ parse(Text t) { - if (!__ftype__.TryParse(t.ToString(), out __ftype__ value)) + if (!__ftype__.TryParse(t.ToString(), __parseNumberStyle__, CultureInfo.InvariantCulture, out __ftype__ value)) success = false; return value; diff --git a/src/Tests/Aardvark.Base.Tests/Math/ColorTests.cs b/src/Tests/Aardvark.Base.Tests/Math/ColorTests.cs index bd95b80c..e4b062e9 100644 --- a/src/Tests/Aardvark.Base.Tests/Math/ColorTests.cs +++ b/src/Tests/Aardvark.Base.Tests/Math/ColorTests.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Linq; using Aardvark.Base; using NUnit.Framework; @@ -245,5 +242,17 @@ public void ParseHexSingleDigit() Assert.AreEqual(a, b); } } + + [Test] + [SetCulture("de-DE")] + public void ParseWithBadCulture() + { + var rnd = new RandomSystem(1); + var col = rnd.UniformC4d(); + var str = col.ToString(); + var res = C4d.Parse(str); + + Assert.True(Fun.ApproximateEquals(col, res)); + } } } From 13d72da1ca58b594381b5c168cf13ffaa167e6d0 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 6 Feb 2024 12:03:08 +0100 Subject: [PATCH 33/45] [FSharp] Add more value variants for dictionary functions See: https://github.com/aardvark-platform/aardvark.base/commit/a9974e8167cbba7050abc34ee14c98234e0d5bef --- .../Utilities/Interop/Dictionary.fs | 135 +++++++++++++----- 1 file changed, 99 insertions(+), 36 deletions(-) diff --git a/src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs index de3340c4..5c52bf26 100644 --- a/src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs +++ b/src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs @@ -1,41 +1,15 @@ namespace Aardvark.Base -[] -module CSharpCollectionExtensions = - open System - open System.Runtime.CompilerServices - open System.Collections.Generic - open System.Runtime.InteropServices - - type public DictionaryExtensions = - - [] - [] - static member TryRemove(x : Dictionary<'a,'b>, k,[] r: byref<'b>) = - match x.TryGetValue k with - | (true,v) -> r <- v; true - | _ -> false - - [] - [] - static member GetOrAdd(x : Dictionary<'a,'b>, k : 'a, creator : 'a -> 'b) = - match x.TryGetValue k with - | (true,v) -> v - | _ -> - let v = creator k - x.Add(k,v) |> ignore - v - [] module Dictionary = open System.Collections.Generic let empty<'k, 'v when 'k : equality> = Dictionary<'k, 'v>() - let emptyNoEquality<'k,'v> = + let emptyNoEquality<'k,'v> = Dictionary<'k,'v>( - { new IEqualityComparer<'k> with + { new IEqualityComparer<'k> with member x.Equals(a,b) = Unchecked.equals a b - member x.GetHashCode(t) = Unchecked.hash t + member x.GetHashCode(t) = Unchecked.hash t }) let inline add (key : 'k) (value : 'v) (d : Dictionary<'k, 'v>) = @@ -50,7 +24,6 @@ module Dictionary = let inline clear (d : Dictionary<'k, 'v>) = d.Clear() - let inline map (f : 'k -> 'a -> 'b) (d : Dictionary<'k, 'a>) = let result = Dictionary() for (KeyValue(k,v)) in d do @@ -75,8 +48,13 @@ module Dictionary = let inline tryFind (key : 'k) (d : Dictionary<'k, 'v>) = match d.TryGetValue key with - | (true, v) -> Some v - | _ -> None + | (true, v) -> Some v + | _ -> None + + let inline tryFindV (key : 'k) (d : Dictionary<'k, 'v>) = + let mutable value = Unchecked.defaultof<_> + if d.TryGetValue(key, &value) then ValueSome value + else ValueNone let inline ofSeq (elements : seq<'k * 'v>) = let result = Dictionary() @@ -84,24 +62,45 @@ module Dictionary = result.[k] <- v result + let inline ofSeqV (elements : seq) = + let result = Dictionary() + for (k,v) in elements do + result.[k] <- v + result + let inline ofList (elements : list<'k * 'v>) = ofSeq elements + let inline ofListV (elements : list) = + ofSeqV elements + let inline ofArray (elements : ('k * 'v)[]) = ofSeq elements + let inline ofArrayV (elements : (struct('k * 'v))[]) = + ofSeqV elements + let inline ofMap (elements : Map<'k, 'v>) = elements |> Map.toSeq |> ofSeq let inline toSeq (d : Dictionary<'k, 'v>) = d |> Seq.map (fun (KeyValue(k,v)) -> k,v) + let inline toSeqV (d : Dictionary<'k, 'v>) = + d |> Seq.map (fun (KeyValue(k,v)) -> struct(k,v)) + let inline toList (d : Dictionary<'k, 'v>) = d |> toSeq |> Seq.toList + let inline toListV (d : Dictionary<'k, 'v>) = + d |> toSeqV |> Seq.toList + let inline toArray (d : Dictionary<'k, 'v>) = d |> toSeq |> Seq.toArray + let inline toArrayV (d : Dictionary<'k, 'v>) = + d |> toSeqV |> Seq.toArray + let inline toMap (d : Dictionary<'k, 'v>) = d |> toSeq |> Map.ofSeq @@ -242,7 +241,6 @@ module SymDict = let inline clear (d : SymbolDict<'v>) = d.Clear() - let inline map (f : Symbol -> 'a -> 'b) (d : SymbolDict<'a>) = let result = SymbolDict() for (KeyValue(k,v)) in d do @@ -267,8 +265,13 @@ module SymDict = let inline tryFind (key : Symbol) (d : SymbolDict<'v>) = match d.TryGetValue key with - | (true, v) -> Some v - | _ -> None + | (true, v) -> Some v + | _ -> None + + let inline tryFindV (key : Symbol) (d : SymbolDict<'v>) = + let mutable value = Unchecked.defaultof<_> + if d.TryGetValue(key, &value) then ValueSome value + else ValueNone let inline ofSeq (elements : seq) = let result = SymbolDict() @@ -276,23 +279,83 @@ module SymDict = result.Add(k,v) result + let inline ofSeqV (elements : seq) = + let result = SymbolDict() + for (k,v) in elements do + result.Add(k,v) + result + let inline ofList (elements : list) = ofSeq elements + let inline ofListV (elements : list) = + ofSeqV elements + let inline ofArray (elements : (Symbol * 'v)[]) = ofSeq elements + let inline ofArrayV (elements : (struct(Symbol * 'v))[]) = + ofSeqV elements + let inline ofMap (elements : Map) = elements |> Map.toSeq |> ofSeq let inline toSeq (d : SymbolDict<'v>) = d |> Seq.map (fun (KeyValue(k,v)) -> k,v) + let inline toSeqV (d : SymbolDict<'v>) = + d |> Seq.map (fun (KeyValue(k,v)) -> struct (k,v)) + let inline toList (d : SymbolDict<'v>) = d |> toSeq |> Seq.toList + let inline toListV (d : SymbolDict<'v>) = + d |> toSeqV |> Seq.toList + let inline toArray (d : SymbolDict<'v>) = - d |> toSeq |> Seq.toArray + d |> toSeq |> Seq.toArray + + let inline toArrayV (d : SymbolDict<'v>) = + d |> toSeqV |> Seq.toArray let inline toMap (elements : SymbolDict<'v>) = elements |> toSeq |> Map.ofSeq + + +[] +module CSharpCollectionExtensions = + open System + open System.Runtime.CompilerServices + open System.Collections.Generic + open System.Runtime.InteropServices + + type Dictionary<'Key, 'Value> with + member inline x.TryFind(key : 'Key) : 'Value option = Dictionary.tryFind key x + member inline x.TryFindV(key : 'Key) : 'Value voption = Dictionary.tryFindV key x + + type Dict<'Key, 'Value> with + member inline x.TryFind(key : 'Key) : 'Value option = Dict.tryFind key x + member inline x.TryFindV(key : 'Key) : 'Value voption = Dict.tryFindV key x + + type SymbolDict<'Value> with + member inline x.TryFind(key : Symbol) : 'Value option = SymDict.tryFind key x + member inline x.TryFindV(key : Symbol) : 'Value voption = SymDict.tryFindV key x + + type public DictionaryExtensions = + + [] + [] + static member TryRemove(x : Dictionary<'a,'b>, k,[] r: byref<'b>) = + match x.TryGetValue k with + | (true,v) -> r <- v; true + | _ -> false + + [] + [] + static member GetOrAdd(x : Dictionary<'a,'b>, k : 'a, creator : 'a -> 'b) = + match x.TryGetValue k with + | (true,v) -> v + | _ -> + let v = creator k + x.Add(k,v) |> ignore + v \ No newline at end of file From b929e1e288fd059d0e0a13b5f14c8bae6a27a2ac Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 6 Feb 2024 12:07:28 +0100 Subject: [PATCH 34/45] Update RELEASE_NOTES.md --- RELEASE_NOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index cc9c20e1..06a1104f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,7 @@ +### 5.2.29 +* Fixed color parsing to be independent of the current culture (regression in 5.2.27) +* Added more value variants for Dictionary, Dict, and SymbolDict functions + ### 5.2.28 * Added Brewer color schemes * Added String.replace From 89e5d12cd53ec713bba05887008abf704d316544 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 27 Feb 2024 12:01:10 +0100 Subject: [PATCH 35/45] [RangeSet] Add containsRange and union --- .../Datastructures/Immutable/RangeSet_auto.fs | 132 +++++++++++++++--- .../Immutable/RangeSet_template.fs | 33 ++++- .../RangeSet.fs | 15 ++ 3 files changed, 160 insertions(+), 20 deletions(-) diff --git a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_auto.fs b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_auto.fs index 0d88761b..20320d66 100644 --- a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_auto.fs +++ b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_auto.fs @@ -202,6 +202,13 @@ type RangeSet1i internal (store : MapExt) = assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) RangeSet1i(newStore) + /// Returns the union of the set with the given set. + member inline x.Union(other : RangeSet1i) = + let mutable res = x + for r in other do + res <- res.Add r + res + /// Returns the intersection of the set with the given range. member x.Intersect(r : Range1i) = if r.Max < r.Min then @@ -223,14 +230,26 @@ type RangeSet1i internal (store : MapExt) = assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) RangeSet1i(newStore) - /// Returns whether the given value is contained in the range set. - member x.Contains(v : int32) = + member private x.TryFindLeftBoundary(v : int32) = let struct (l, s, _) = MapExt.neighboursV v store match s with - | ValueSome (_, k) -> k = HalfRangeKind.Left + | ValueSome (i, k) -> if k = HalfRangeKind.Left then ValueSome i else ValueNone | _ -> match l with - | ValueSome (_, HalfRangeKind.Left) -> true + | ValueSome (i, HalfRangeKind.Left) -> ValueSome i + | _ -> ValueNone + + /// Returns whether the given value is contained in the range set. + member x.Contains(v : int32) = + x.TryFindLeftBoundary v |> ValueOption.isSome + + /// Returns whether the given range is contained in the set. + member x.Contains(r : Range1i) = + if r.Max < r.Min then false + elif r.Min = r.Max then x.Contains r.Min + else + match x.TryFindLeftBoundary r.Min, x.TryFindLeftBoundary r.Max with + | ValueSome l, ValueSome r -> l = r | _ -> false /// Returns the number of disjoint ranges in the set. @@ -435,6 +454,9 @@ module RangeSet1i = /// Removes the given range from the set. let inline remove (range : Range1i) (set : RangeSet1i) = set.Remove range + /// Returns the union of two sets. + let inline union (l : RangeSet1i) (r : RangeSet1i) = l.Union r + /// Returns the intersection of the set with the given range. let inline intersect (range : Range1i) (set : RangeSet1i) = set.Intersect range @@ -444,6 +466,9 @@ module RangeSet1i = /// Returns whether the given value is contained in the range set. let inline contains (value : int32) (set : RangeSet1i) = set.Contains value + /// Returns whether the given range is contained in the set. + let inline containsRange (range : Range1i) (set : RangeSet1i) = set.Contains range + /// Returns the number of disjoint ranges in the set. let inline count (set : RangeSet1i) = set.Count @@ -630,6 +655,13 @@ type RangeSet1ui internal (store : MapExt) = assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) RangeSet1ui(newStore) + /// Returns the union of the set with the given set. + member inline x.Union(other : RangeSet1ui) = + let mutable res = x + for r in other do + res <- res.Add r + res + /// Returns the intersection of the set with the given range. member x.Intersect(r : Range1ui) = if r.Max < r.Min then @@ -651,14 +683,26 @@ type RangeSet1ui internal (store : MapExt) = assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) RangeSet1ui(newStore) - /// Returns whether the given value is contained in the range set. - member x.Contains(v : uint32) = + member private x.TryFindLeftBoundary(v : uint32) = let struct (l, s, _) = MapExt.neighboursV v store match s with - | ValueSome (_, k) -> k = HalfRangeKind.Left + | ValueSome (i, k) -> if k = HalfRangeKind.Left then ValueSome i else ValueNone | _ -> match l with - | ValueSome (_, HalfRangeKind.Left) -> true + | ValueSome (i, HalfRangeKind.Left) -> ValueSome i + | _ -> ValueNone + + /// Returns whether the given value is contained in the range set. + member x.Contains(v : uint32) = + x.TryFindLeftBoundary v |> ValueOption.isSome + + /// Returns whether the given range is contained in the set. + member x.Contains(r : Range1ui) = + if r.Max < r.Min then false + elif r.Min = r.Max then x.Contains r.Min + else + match x.TryFindLeftBoundary r.Min, x.TryFindLeftBoundary r.Max with + | ValueSome l, ValueSome r -> l = r | _ -> false /// Returns the number of disjoint ranges in the set. @@ -863,6 +907,9 @@ module RangeSet1ui = /// Removes the given range from the set. let inline remove (range : Range1ui) (set : RangeSet1ui) = set.Remove range + /// Returns the union of two sets. + let inline union (l : RangeSet1ui) (r : RangeSet1ui) = l.Union r + /// Returns the intersection of the set with the given range. let inline intersect (range : Range1ui) (set : RangeSet1ui) = set.Intersect range @@ -872,6 +919,9 @@ module RangeSet1ui = /// Returns whether the given value is contained in the range set. let inline contains (value : uint32) (set : RangeSet1ui) = set.Contains value + /// Returns whether the given range is contained in the set. + let inline containsRange (range : Range1ui) (set : RangeSet1ui) = set.Contains range + /// Returns the number of disjoint ranges in the set. let inline count (set : RangeSet1ui) = set.Count @@ -1058,6 +1108,13 @@ type RangeSet1l internal (store : MapExt) = assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) RangeSet1l(newStore) + /// Returns the union of the set with the given set. + member inline x.Union(other : RangeSet1l) = + let mutable res = x + for r in other do + res <- res.Add r + res + /// Returns the intersection of the set with the given range. member x.Intersect(r : Range1l) = if r.Max < r.Min then @@ -1079,14 +1136,26 @@ type RangeSet1l internal (store : MapExt) = assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) RangeSet1l(newStore) - /// Returns whether the given value is contained in the range set. - member x.Contains(v : int64) = + member private x.TryFindLeftBoundary(v : int64) = let struct (l, s, _) = MapExt.neighboursV v store match s with - | ValueSome (_, k) -> k = HalfRangeKind.Left + | ValueSome (i, k) -> if k = HalfRangeKind.Left then ValueSome i else ValueNone | _ -> match l with - | ValueSome (_, HalfRangeKind.Left) -> true + | ValueSome (i, HalfRangeKind.Left) -> ValueSome i + | _ -> ValueNone + + /// Returns whether the given value is contained in the range set. + member x.Contains(v : int64) = + x.TryFindLeftBoundary v |> ValueOption.isSome + + /// Returns whether the given range is contained in the set. + member x.Contains(r : Range1l) = + if r.Max < r.Min then false + elif r.Min = r.Max then x.Contains r.Min + else + match x.TryFindLeftBoundary r.Min, x.TryFindLeftBoundary r.Max with + | ValueSome l, ValueSome r -> l = r | _ -> false /// Returns the number of disjoint ranges in the set. @@ -1291,6 +1360,9 @@ module RangeSet1l = /// Removes the given range from the set. let inline remove (range : Range1l) (set : RangeSet1l) = set.Remove range + /// Returns the union of two sets. + let inline union (l : RangeSet1l) (r : RangeSet1l) = l.Union r + /// Returns the intersection of the set with the given range. let inline intersect (range : Range1l) (set : RangeSet1l) = set.Intersect range @@ -1300,6 +1372,9 @@ module RangeSet1l = /// Returns whether the given value is contained in the range set. let inline contains (value : int64) (set : RangeSet1l) = set.Contains value + /// Returns whether the given range is contained in the set. + let inline containsRange (range : Range1l) (set : RangeSet1l) = set.Contains range + /// Returns the number of disjoint ranges in the set. let inline count (set : RangeSet1l) = set.Count @@ -1486,6 +1561,13 @@ type RangeSet1ul internal (store : MapExt) = assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) RangeSet1ul(newStore) + /// Returns the union of the set with the given set. + member inline x.Union(other : RangeSet1ul) = + let mutable res = x + for r in other do + res <- res.Add r + res + /// Returns the intersection of the set with the given range. member x.Intersect(r : Range1ul) = if r.Max < r.Min then @@ -1507,14 +1589,26 @@ type RangeSet1ul internal (store : MapExt) = assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) RangeSet1ul(newStore) - /// Returns whether the given value is contained in the range set. - member x.Contains(v : uint64) = + member private x.TryFindLeftBoundary(v : uint64) = let struct (l, s, _) = MapExt.neighboursV v store match s with - | ValueSome (_, k) -> k = HalfRangeKind.Left + | ValueSome (i, k) -> if k = HalfRangeKind.Left then ValueSome i else ValueNone | _ -> match l with - | ValueSome (_, HalfRangeKind.Left) -> true + | ValueSome (i, HalfRangeKind.Left) -> ValueSome i + | _ -> ValueNone + + /// Returns whether the given value is contained in the range set. + member x.Contains(v : uint64) = + x.TryFindLeftBoundary v |> ValueOption.isSome + + /// Returns whether the given range is contained in the set. + member x.Contains(r : Range1ul) = + if r.Max < r.Min then false + elif r.Min = r.Max then x.Contains r.Min + else + match x.TryFindLeftBoundary r.Min, x.TryFindLeftBoundary r.Max with + | ValueSome l, ValueSome r -> l = r | _ -> false /// Returns the number of disjoint ranges in the set. @@ -1719,6 +1813,9 @@ module RangeSet1ul = /// Removes the given range from the set. let inline remove (range : Range1ul) (set : RangeSet1ul) = set.Remove range + /// Returns the union of two sets. + let inline union (l : RangeSet1ul) (r : RangeSet1ul) = l.Union r + /// Returns the intersection of the set with the given range. let inline intersect (range : Range1ul) (set : RangeSet1ul) = set.Intersect range @@ -1728,6 +1825,9 @@ module RangeSet1ul = /// Returns whether the given value is contained in the range set. let inline contains (value : uint64) (set : RangeSet1ul) = set.Contains value + /// Returns whether the given range is contained in the set. + let inline containsRange (range : Range1ul) (set : RangeSet1ul) = set.Contains range + /// Returns the number of disjoint ranges in the set. let inline count (set : RangeSet1ul) = set.Count diff --git a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_template.fs b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_template.fs index 883dcbce..412f4e75 100644 --- a/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_template.fs +++ b/src/Aardvark.Base.FSharp/Datastructures/Immutable/RangeSet_template.fs @@ -216,6 +216,13 @@ type __rangeset__ internal (store : MapExt<__ltype__, HalfRangeKind>) = assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) __rangeset__(newStore) + /// Returns the union of the set with the given set. + member inline x.Union(other : __rangeset__) = + let mutable res = x + for r in other do + res <- res.Add r + res + /// Returns the intersection of the set with the given range. member x.Intersect(r : __range__) = if r.Max < r.Min then @@ -237,14 +244,26 @@ type __rangeset__ internal (store : MapExt<__ltype__, HalfRangeKind>) = assert (newStore.Count % 2 = 0 || MapExt.maxValue newStore = HalfRangeKind.Left) __rangeset__(newStore) - /// Returns whether the given value is contained in the range set. - member x.Contains(v : __ltype__) = + member private x.TryFindLeftBoundary(v : __ltype__) = let struct (l, s, _) = MapExt.neighboursV v store match s with - | ValueSome (_, k) -> k = HalfRangeKind.Left + | ValueSome (i, k) -> if k = HalfRangeKind.Left then ValueSome i else ValueNone | _ -> match l with - | ValueSome (_, HalfRangeKind.Left) -> true + | ValueSome (i, HalfRangeKind.Left) -> ValueSome i + | _ -> ValueNone + + /// Returns whether the given value is contained in the range set. + member x.Contains(v : __ltype__) = + x.TryFindLeftBoundary v |> ValueOption.isSome + + /// Returns whether the given range is contained in the set. + member x.Contains(r : __range__) = + if r.Max < r.Min then false + elif r.Min = r.Max then x.Contains r.Min + else + match x.TryFindLeftBoundary r.Min, x.TryFindLeftBoundary r.Max with + | ValueSome l, ValueSome r -> l = r | _ -> false /// Returns the number of disjoint ranges in the set. @@ -449,6 +468,9 @@ module __rangeset__ = /// Removes the given range from the set. let inline remove (range : __range__) (set : __rangeset__) = set.Remove range + /// Returns the union of two sets. + let inline union (l : __rangeset__) (r : __rangeset__) = l.Union r + /// Returns the intersection of the set with the given range. let inline intersect (range : __range__) (set : __rangeset__) = set.Intersect range @@ -458,6 +480,9 @@ module __rangeset__ = /// Returns whether the given value is contained in the range set. let inline contains (value : __ltype__) (set : __rangeset__) = set.Contains value + /// Returns whether the given range is contained in the set. + let inline containsRange (range : __range__) (set : __rangeset__) = set.Contains range + /// Returns the number of disjoint ranges in the set. let inline count (set : __rangeset__) = set.Count diff --git a/src/Tests/Aardvark.Base.FSharp.Benchmarks/RangeSet.fs b/src/Tests/Aardvark.Base.FSharp.Benchmarks/RangeSet.fs index aabff5a4..a8475a98 100644 --- a/src/Tests/Aardvark.Base.FSharp.Benchmarks/RangeSet.fs +++ b/src/Tests/Aardvark.Base.FSharp.Benchmarks/RangeSet.fs @@ -205,6 +205,21 @@ module RangeSetTests = let actual = RangeSet1l.ofList ranges actual |> should equal expected + [] + let ``[RangeSet] Contains Range`` (maxValue : bool) = + let set = RangeSet1l.ofList [ Range1l(-3L, -2L); Range1l(1L, 4L); Range1l(6L, 8L); Range1l(10L, if maxValue then Int64.MaxValue else 30L)] + + set |> RangeSet1l.containsRange (Range1l(-3L, -2L)) |> should be True + set |> RangeSet1l.containsRange (Range1l(-3L, -3L)) |> should be True + set |> RangeSet1l.containsRange (Range1l(-3L, -2L)) |> should be True + set |> RangeSet1l.containsRange (Range1l(-2L, -2L)) |> should be True + + set |> RangeSet1l.containsRange (Range1l(2L, 3L)) |> should be True + set |> RangeSet1l.containsRange (Range1l(3L, 3L)) |> should be True + + set |> RangeSet1l.containsRange (Range1l(-2L, 3L)) |> should be False + + set |> RangeSet1l.containsRange (Range1l(20L, Int64.MaxValue)) |> should equal maxValue module RangeSetBenchmarks = open BenchmarkDotNet.Attributes; From 3b787e9843397a377bd351a02babbc448f541bb7 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 21 Mar 2024 14:18:48 +0100 Subject: [PATCH 36/45] [PixImage] Fix comment of AddLoader --- src/Aardvark.Base.Tensors.CSharp/PixImage/PixImage.cs | 2 +- src/Tests/Aardvark.Base.Windows.Tests/PixLoaderTests.fs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Aardvark.Base.Tensors.CSharp/PixImage/PixImage.cs b/src/Aardvark.Base.Tensors.CSharp/PixImage/PixImage.cs index 513491f3..16e8d2c9 100644 --- a/src/Aardvark.Base.Tensors.CSharp/PixImage/PixImage.cs +++ b/src/Aardvark.Base.Tensors.CSharp/PixImage/PixImage.cs @@ -275,7 +275,7 @@ public static void SetLoader(IPixLoader loader, int priority) /// /// Adds a PixImage loader. /// Assigns a priority that is greater than the highest priority among existing loaders, resulting in a LIFO order. - /// If the loader already exists, the priority is not modified. + /// If the loader already exists, the priority is modified. /// /// The loader to add. public static void AddLoader(IPixLoader loader) diff --git a/src/Tests/Aardvark.Base.Windows.Tests/PixLoaderTests.fs b/src/Tests/Aardvark.Base.Windows.Tests/PixLoaderTests.fs index 4842b97a..0e641a66 100644 --- a/src/Tests/Aardvark.Base.Windows.Tests/PixLoaderTests.fs +++ b/src/Tests/Aardvark.Base.Windows.Tests/PixLoaderTests.fs @@ -285,6 +285,10 @@ module PixLoaderTests = priorities.Count |> should equal count priorities.Get(PixImageDevil.Loader) |> should equal 1337 + PixImage.AddLoader(PixImageDevil.Loader) + let priorities = PixImage.GetLoadersWithPriority() + priorities.Get(PixImageDevil.Loader) |> should equal 1338 + PixImage.RemoveLoader(PixImageSharp.Loader) PixImage.RemoveLoader(PixImageDevil.Loader) PixImage.GetLoaders() |> Seq.length |> should equal (count - 2) From e1ab96abef6a62af06e835bd2cc70b8b0d7e8e8c Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 21 Mar 2024 14:39:08 +0100 Subject: [PATCH 37/45] [PgmPixLoader] Avoid throwing exceptions when trying to load --- .../PixImage/PixImage.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Aardvark.Base.Tensors.CSharp/PixImage/PixImage.cs b/src/Aardvark.Base.Tensors.CSharp/PixImage/PixImage.cs index 16e8d2c9..4f96066e 100644 --- a/src/Aardvark.Base.Tensors.CSharp/PixImage/PixImage.cs +++ b/src/Aardvark.Base.Tensors.CSharp/PixImage/PixImage.cs @@ -205,6 +205,9 @@ public PixImageInfo GetInfoFromStream(Stream stream) #region PgmPixLoader + /// + /// Loader for saving PGM images. Reading is not supported. + /// private class PgmPixLoader : IPixLoader { public string Name { get; } @@ -214,18 +217,14 @@ public PgmPixLoader() Name = "Aardvark PGM"; } - public PixImage LoadFromFile(string filename) - => throw new ImageLoadException($"{Name} loader does not support loading from files."); + public PixImage LoadFromFile(string filename) => null; - public PixImage LoadFromStream(Stream stream) - => throw new ImageLoadException($"{Name} loader does not support loading from streams."); + public PixImage LoadFromStream(Stream stream) => null; public void SaveToFile(string filename, PixImage image, PixSaveParams saveParams) { - using (var stream = File.OpenWrite(filename)) - { - SaveToStream(stream, image, saveParams); - } + using var stream = File.OpenWrite(filename); + SaveToStream(stream, image, saveParams); } public void SaveToStream(Stream stream, PixImage image, PixSaveParams saveParams) @@ -245,11 +244,9 @@ public void SaveToStream(Stream stream, PixImage image, PixSaveParams saveParams stream.Write(img.Volume.Data, 0, img.Volume.Data.Length); } - public PixImageInfo GetInfoFromFile(string filename) - => throw new ImageLoadException($"{Name} loader does not support retrieving info from files."); + public PixImageInfo GetInfoFromFile(string filename) => null; - public PixImageInfo GetInfoFromStream(Stream stream) - => throw new ImageLoadException($"{Name} loader does not support retrieving info from streams."); + public PixImageInfo GetInfoFromStream(Stream stream) => null; } #endregion From b76ee764ad3c97e8b402b50dab7bc24c6c2ad3fc Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 21 Mar 2024 14:52:43 +0100 Subject: [PATCH 38/45] [Color] Remove obsolete warning from Parse overload Overload with bracketLevel parameter is still being used in some projects. --- src/Aardvark.Base/Math/Colors/Color_auto.cs | 10 ---------- src/Aardvark.Base/Math/Colors/Color_template.cs | 1 - 2 files changed, 11 deletions(-) diff --git a/src/Aardvark.Base/Math/Colors/Color_auto.cs b/src/Aardvark.Base/Math/Colors/Color_auto.cs index fbe75caa..1617c305 100644 --- a/src/Aardvark.Base/Math/Colors/Color_auto.cs +++ b/src/Aardvark.Base/Math/Colors/Color_auto.cs @@ -1671,7 +1671,6 @@ public static C3b Parse(string s, IFormatProvider provider) public static C3b Parse(string s) => Parse(new Text(s)); - [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3b Parse(Text t, int bracketLevel = 1) => t.NestedBracketSplit(bracketLevel, Text.Parse, C3b.Setter); @@ -3946,7 +3945,6 @@ public static C3us Parse(string s, IFormatProvider provider) public static C3us Parse(string s) => Parse(new Text(s)); - [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3us Parse(Text t, int bracketLevel = 1) => t.NestedBracketSplit(bracketLevel, Text.Parse, C3us.Setter); @@ -6144,7 +6142,6 @@ public static C3ui Parse(string s, IFormatProvider provider) public static C3ui Parse(string s) => Parse(new Text(s)); - [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3ui Parse(Text t, int bracketLevel = 1) => t.NestedBracketSplit(bracketLevel, Text.Parse, C3ui.Setter); @@ -8217,7 +8214,6 @@ public static C3f Parse(string s, IFormatProvider provider) public static C3f Parse(string s) => Parse(new Text(s)); - [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3f Parse(Text t, int bracketLevel = 1) => t.NestedBracketSplit(bracketLevel, Text.Parse, C3f.Setter); @@ -10327,7 +10323,6 @@ public static C3d Parse(string s, IFormatProvider provider) public static C3d Parse(string s) => Parse(new Text(s)); - [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C3d Parse(Text t, int bracketLevel = 1) => t.NestedBracketSplit(bracketLevel, Text.Parse, C3d.Setter); @@ -12957,7 +12952,6 @@ public static C4b Parse(string s, IFormatProvider provider) public static C4b Parse(string s) => Parse(new Text(s)); - [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4b Parse(Text t, int bracketLevel = 1) => t.NestedBracketSplit(bracketLevel, Text.Parse, C4b.Setter); @@ -15516,7 +15510,6 @@ public static C4us Parse(string s, IFormatProvider provider) public static C4us Parse(string s) => Parse(new Text(s)); - [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4us Parse(Text t, int bracketLevel = 1) => t.NestedBracketSplit(bracketLevel, Text.Parse, C4us.Setter); @@ -17980,7 +17973,6 @@ public static C4ui Parse(string s, IFormatProvider provider) public static C4ui Parse(string s) => Parse(new Text(s)); - [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4ui Parse(Text t, int bracketLevel = 1) => t.NestedBracketSplit(bracketLevel, Text.Parse, C4ui.Setter); @@ -20234,7 +20226,6 @@ public static C4f Parse(string s, IFormatProvider provider) public static C4f Parse(string s) => Parse(new Text(s)); - [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4f Parse(Text t, int bracketLevel = 1) => t.NestedBracketSplit(bracketLevel, Text.Parse, C4f.Setter); @@ -22528,7 +22519,6 @@ public static C4d Parse(string s, IFormatProvider provider) public static C4d Parse(string s) => Parse(new Text(s)); - [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static C4d Parse(Text t, int bracketLevel = 1) => t.NestedBracketSplit(bracketLevel, Text.Parse, C4d.Setter); diff --git a/src/Aardvark.Base/Math/Colors/Color_template.cs b/src/Aardvark.Base/Math/Colors/Color_template.cs index b36e2eae..2413e211 100644 --- a/src/Aardvark.Base/Math/Colors/Color_template.cs +++ b/src/Aardvark.Base/Math/Colors/Color_template.cs @@ -1218,7 +1218,6 @@ public static __type__ Parse(string s, IFormatProvider provider) public static __type__ Parse(string s) => Parse(new Text(s)); - [Obsolete("Weird overload with level, call NestedBracketSplit() manually instead.")] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static __type__ Parse(Text t, int bracketLevel = 1) => t.NestedBracketSplit(bracketLevel, Text<__ftype__>.Parse, __type__.Setter); From 1ee5e5f4f3326b55bc63633a85e4841482571394 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 25 Mar 2024 19:05:43 +0100 Subject: [PATCH 39/45] [Tests] Add benchmark for tensor math extensions The benchmark indicates that tensor math extensions should be implemented inline rather than using tensor methods that take lambda functions as arguments. --- .../TensorMathBenchmark.cs | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 src/Tests/Aardvark.Base.Benchmarks/TensorMathBenchmark.cs diff --git a/src/Tests/Aardvark.Base.Benchmarks/TensorMathBenchmark.cs b/src/Tests/Aardvark.Base.Benchmarks/TensorMathBenchmark.cs new file mode 100644 index 00000000..660b179a --- /dev/null +++ b/src/Tests/Aardvark.Base.Benchmarks/TensorMathBenchmark.cs @@ -0,0 +1,108 @@ +using Aardvark.Base.IL; +using BenchmarkDotNet.Attributes; +using System; +using System.Runtime.CompilerServices; +using static Aardvark.Base.Monads.Optics; +using System.Runtime.Intrinsics.X86; + +namespace Aardvark.Base.Benchmarks +{ + /* + * Benchmark indicates that inlined implementations are noticeably more efficient than + * simply using Norm or InnerProduct and passing lambda functions. Therefore it may be wise to implement + * common tensor operations in a dumb inline way for optimization purposes. The InlineIfLambda attribute + * in F# might mitigate this but we are dealing with C# here. + * + * BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, Windows 10 (10.0.19045.4170/22H2/2022Update) + * AMD Ryzen 5 5600X, 1 CPU, 12 logical and 6 physical cores + * .NET SDK 6.0.420 + * [Host] : .NET 6.0.28 (6.0.2824.12007), X64 RyuJIT AVX2 + * + * Job = InProcess Toolchain=InProcessEmitToolchain + * + * | Method | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio | + * |--------------------------- |-----------:|---------:|---------:|------:|--------:|----------:|------------:| + * | NormSquared | 1,377.5 us | 24.30 us | 23.87 us | 1.00 | 0.00 | 6 B | 1.00 | + * | 'NormSquared (Predefined)' | 1,771.9 us | 35.15 us | 40.48 us | 1.28 | 0.04 | 1 B | 0.17 | + * | 'NormSquared (Inline)' | 340.1 us | 1.77 us | 1.48 us | 0.25 | 0.00 | - | 0.00 | + */ + + [InProcess] + [MemoryDiagnoser] + public class VectorNormSquared + { + private static readonly Func SquareFunc = Fun.Square; + private static readonly Func AddFunc = (x, y) => x + y; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float NormSquared(Vector vector) + => vector.Norm(x => x * x, 0.0f, (x, y) => x + y); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float NormSquaredPredefined(Vector vector) + => vector.Norm(SquareFunc, 0.0f, AddFunc); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float NormSquaredInline(Vector v) + { + float result = 0.0f; + long i = v.FirstIndex; + if (v.Info.DX == 1) + { + long xs = v.Info.SX; + for (long xe = i + xs; i != xe; i++) + { + result += v.Data[i] * v.Data[i]; + } + } + else + { + long xs = v.Info.DSX, xj = v.Info.JX; + for (long xe = i + xs; i != xe; i += xj) + { + result += v.Data[i] * v.Data[i]; + } + } + return result; + } + + private static Vector GenerateVector(RandomSystem rnd) + => Vector.Create(new float[4 + rnd.UniformInt(1000)].SetByIndex(i => rnd.UniformFloat())); + + Vector[] _data; + + [GlobalSetup] + public void Init() + { + var rnd = new RandomSystem(0); + _data = new Vector[1000].SetByIndex(i => GenerateVector(rnd)); + } + + [Benchmark(Baseline = true, Description = "NormSquared")] + public float NormSquared() + { + float sum = 0; + foreach (var x in _data) + sum += NormSquared(x); + return sum; + } + + [Benchmark(Description = "NormSquared (Predefined)")] + public float NormSquaredPredfined() + { + float sum = 0; + foreach (var x in _data) + sum += NormSquaredPredefined(x); + return sum; + } + + [Benchmark(Description = "NormSquared (Inline)")] + public float NormSquaredInline() + { + float sum = 0; + foreach (var x in _data) + sum += NormSquaredInline(x); + return sum; + } + } +} From c3e78251c6243976dd09616a1408af822aa76c8c Mon Sep 17 00:00:00 2001 From: Christian Luksch Date: Wed, 8 May 2024 18:13:49 +0200 Subject: [PATCH 40/45] [IO] fixed occasional exception in ReadArray observed on net 8.0 runtime --- src/Aardvark.Base.IO/Aardvark.Base.IO.csproj | 2 +- src/Aardvark.Base.IO/StreamCodeReader.cs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Aardvark.Base.IO/Aardvark.Base.IO.csproj b/src/Aardvark.Base.IO/Aardvark.Base.IO.csproj index 6f540866..a77a9a50 100644 --- a/src/Aardvark.Base.IO/Aardvark.Base.IO.csproj +++ b/src/Aardvark.Base.IO/Aardvark.Base.IO.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + net6.0;netstandard2.0 true 612;1591 true diff --git a/src/Aardvark.Base.IO/StreamCodeReader.cs b/src/Aardvark.Base.IO/StreamCodeReader.cs index adc11982..7a37a6de 100644 --- a/src/Aardvark.Base.IO/StreamCodeReader.cs +++ b/src/Aardvark.Base.IO/StreamCodeReader.cs @@ -123,6 +123,25 @@ public long ReadArray(T[] array, long index, long count) where T : struct { if (count < 1) return 0; + +#if NET6_0_OR_GREATER + var span = MemoryMarshal.AsBytes(array.AsSpan((int)index, (int)count)); + + var sizeOfT = span.Length / array.Length; + var bytesToRead = span.Length; + var offset = 0; + + do + { + int finished = base.Read(span); + if (finished == 0) break; + offset += finished; bytesToRead -= finished; + span = span.Slice(offset, bytesToRead); + } + while (bytesToRead > 0); + + return offset / sizeOfT; +#else unsafe { var sizeOfT = Marshal.SizeOf(typeof(T)); @@ -152,6 +171,7 @@ public long ReadArray(T[] array, long index, long count) } return ((long)(offset / sizeOfT) - index); } +#endif } public long ReadArray(T[,] array, long count) From 4af328ddf200df2b9ea3e70db6a1d14b657722f6 Mon Sep 17 00:00:00 2001 From: Christian Luksch Date: Mon, 13 May 2024 17:16:29 +0200 Subject: [PATCH 41/45] [IO] added WriteArray net6 code path using span --- src/Aardvark.Base.IO/StreamCodeWriter.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Aardvark.Base.IO/StreamCodeWriter.cs b/src/Aardvark.Base.IO/StreamCodeWriter.cs index 67ce5795..9cbfe925 100644 --- a/src/Aardvark.Base.IO/StreamCodeWriter.cs +++ b/src/Aardvark.Base.IO/StreamCodeWriter.cs @@ -123,6 +123,12 @@ public void WriteArray(T[] array, long index, long count) where T : struct { if (count < 1) return; + +#if NET6_0_OR_GREATER + var arrSpan = array.AsSpan((int)index, (int)count); + var byteSpan = MemoryMarshal.AsBytes(arrSpan); + base.Write(byteSpan); +#else unsafe { var sizeOfT = Marshal.SizeOf(typeof(T)); @@ -152,6 +158,7 @@ public void WriteArray(T[] array, long index, long count) while (count > 0); } } +#endif } public void WriteArray(T[,] array, long count) From d11a624a340d34c9b836a268562c5dac53a52563 Mon Sep 17 00:00:00 2001 From: Christian Luksch Date: Mon, 13 May 2024 18:14:13 +0200 Subject: [PATCH 42/45] [IO] using spans in all Read/WriteArray functions with net6.0 --- src/Aardvark.Base.IO/StreamCodeReader.cs | 54 ++++++++++++++++++++---- src/Aardvark.Base.IO/StreamCodeWriter.cs | 25 ++++++----- 2 files changed, 60 insertions(+), 19 deletions(-) diff --git a/src/Aardvark.Base.IO/StreamCodeReader.cs b/src/Aardvark.Base.IO/StreamCodeReader.cs index 7a37a6de..79e62821 100644 --- a/src/Aardvark.Base.IO/StreamCodeReader.cs +++ b/src/Aardvark.Base.IO/StreamCodeReader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -178,6 +179,25 @@ public long ReadArray(T[,] array, long count) where T : struct { if (count < 1) return 0; + +#if NET6_0_OR_GREATER + var sizeOfT = Marshal.SizeOf(typeof(T)); + var span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(array), (int)count * sizeOfT); + + var bytesToRead = span.Length; + var offset = 0; + + do + { + int finished = base.Read(span); + if (finished == 0) break; + offset += finished; bytesToRead -= finished; + span = span.Slice(offset, bytesToRead); + } + while (bytesToRead > 0); + + return offset / sizeOfT; +#else unsafe { var sizeOfT = Marshal.SizeOf(typeof(T)); @@ -185,11 +205,9 @@ public long ReadArray(T[,] array, long count) hack.structs = array; var bytesToRead = (int)(sizeOfT * count); - #if __MonoCS__ - var skip = 0; - #else + var skip = 2 * 2 * sizeof(int); - #endif + IntPtr byteLen = (IntPtr)(array.Length * sizeOfT + skip); var offset = skip; @@ -212,12 +230,31 @@ public long ReadArray(T[,] array, long count) } return (long)((offset - skip) / sizeOfT); } +#endif } public long ReadArray(T[, ,] array, long count) where T : struct { if (count < 1) return 0; +#if NET6_0_OR_GREATER + var sizeOfT = Marshal.SizeOf(typeof(T)); + var span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(array), (int)count * sizeOfT); + + var bytesToRead = span.Length; + var offset = 0; + + do + { + int finished = base.Read(span); + if (finished == 0) break; + offset += finished; bytesToRead -= finished; + span = span.Slice(offset, bytesToRead); + } + while (bytesToRead > 0); + + return offset / sizeOfT; +#else unsafe { var sizeOfT = Marshal.SizeOf(typeof(T)); @@ -225,11 +262,9 @@ public long ReadArray(T[, ,] array, long count) hack.structs = array; var bytesToRead = (int)(sizeOfT * count); - #if __MonoCS__ - var skip = 0; - #else + var skip = 3 * 2 * sizeof(int); - #endif + IntPtr byteLen = (IntPtr)(array.Length * sizeOfT + skip); var offset = skip; @@ -252,6 +287,7 @@ public long ReadArray(T[, ,] array, long count) } return (long)((offset - skip) / sizeOfT); } +#endif } public int ReadList(List buffer, int index, int count) @@ -264,7 +300,7 @@ public int ReadList(List buffer, int index, int count) return (int)ReadArray(arrayValue, (long)index, (long)count); } - #endregion +#endregion #region Close diff --git a/src/Aardvark.Base.IO/StreamCodeWriter.cs b/src/Aardvark.Base.IO/StreamCodeWriter.cs index 9cbfe925..f4d07215 100644 --- a/src/Aardvark.Base.IO/StreamCodeWriter.cs +++ b/src/Aardvark.Base.IO/StreamCodeWriter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -165,6 +166,11 @@ public void WriteArray(T[,] array, long count) where T : struct { if (count < 1) return; +#if NET6_0_OR_GREATER + var sizeOfT = Marshal.SizeOf(typeof(T)); + var byteSpan = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(array), (int)count * sizeOfT); + base.Write(byteSpan); +#else unsafe { var sizeOfT = Marshal.SizeOf(typeof(T)); @@ -174,11 +180,7 @@ public void WriteArray(T[,] array, long count) fixed (byte* pBytes = hack.bytes) { - #if __MonoCS__ - long offset = 0; - #else long offset = 2 * 2 * sizeof(int); - #endif int itemsPerBlock = c_bufferSize / sizeOfT; do @@ -198,12 +200,18 @@ public void WriteArray(T[,] array, long count) while (count > 0); } } +#endif } public void WriteArray(T[, ,] array, long count) where T : struct { if (count < 1) return; +#if NET6_0_OR_GREATER + var sizeOfT = Marshal.SizeOf(typeof(T)); + var byteSpan = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(array), (int)count * sizeOfT); + base.Write(byteSpan); +#else unsafe { var sizeOfT = Marshal.SizeOf(typeof(T)); @@ -213,12 +221,8 @@ public void WriteArray(T[, ,] array, long count) fixed (byte* pBytes = hack.bytes) { - #if __MonoCS__ - long offset = 0; - #else long offset = 3 * 2 * sizeof(int); - #endif - + int itemsPerBlock = c_bufferSize / sizeOfT; do { @@ -237,6 +241,7 @@ public void WriteArray(T[, ,] array, long count) while (count > 0); } } +#endif } public void WriteList(List buffer, int index, int count) @@ -247,7 +252,7 @@ public void WriteList(List buffer, int index, int count) WriteArray(arrayValue, (long)index, (long)count); } - #endregion +#endregion #region Close From 4c2f1a1a88061889653a3a1dcc45f782a3375cc0 Mon Sep 17 00:00:00 2001 From: Christian Luksch Date: Mon, 13 May 2024 18:20:28 +0200 Subject: [PATCH 43/45] [IO] replaced Marshal.SizeOf with Unsafe.SizeOf --- src/Aardvark.Base.IO/StreamCodeReader.cs | 10 +++++----- src/Aardvark.Base.IO/StreamCodeWriter.cs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Aardvark.Base.IO/StreamCodeReader.cs b/src/Aardvark.Base.IO/StreamCodeReader.cs index 79e62821..ce07af4a 100644 --- a/src/Aardvark.Base.IO/StreamCodeReader.cs +++ b/src/Aardvark.Base.IO/StreamCodeReader.cs @@ -145,7 +145,7 @@ public long ReadArray(T[] array, long index, long count) #else unsafe { - var sizeOfT = Marshal.SizeOf(typeof(T)); + var sizeOfT = Unsafe.SizeOf(); var hack = new ByteArrayUnion(); hack.structs = array; @@ -181,7 +181,7 @@ public long ReadArray(T[,] array, long count) if (count < 1) return 0; #if NET6_0_OR_GREATER - var sizeOfT = Marshal.SizeOf(typeof(T)); + var sizeOfT = Unsafe.SizeOf(); var span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(array), (int)count * sizeOfT); var bytesToRead = span.Length; @@ -200,7 +200,7 @@ public long ReadArray(T[,] array, long count) #else unsafe { - var sizeOfT = Marshal.SizeOf(typeof(T)); + var sizeOfT = Unsafe.SizeOf(); var hack = new ByteArrayUnion(); hack.structs = array; @@ -238,7 +238,7 @@ public long ReadArray(T[, ,] array, long count) { if (count < 1) return 0; #if NET6_0_OR_GREATER - var sizeOfT = Marshal.SizeOf(typeof(T)); + var sizeOfT = Unsafe.SizeOf(); var span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(array), (int)count * sizeOfT); var bytesToRead = span.Length; @@ -257,7 +257,7 @@ public long ReadArray(T[, ,] array, long count) #else unsafe { - var sizeOfT = Marshal.SizeOf(typeof(T)); + var sizeOfT = Unsafe.SizeOf(); var hack = new ByteArrayUnion(); hack.structs = array; diff --git a/src/Aardvark.Base.IO/StreamCodeWriter.cs b/src/Aardvark.Base.IO/StreamCodeWriter.cs index f4d07215..6a765d9a 100644 --- a/src/Aardvark.Base.IO/StreamCodeWriter.cs +++ b/src/Aardvark.Base.IO/StreamCodeWriter.cs @@ -132,7 +132,7 @@ public void WriteArray(T[] array, long index, long count) #else unsafe { - var sizeOfT = Marshal.SizeOf(typeof(T)); + var sizeOfT = Unsafe.SizeOf(); var hack = new ByteArrayUnion(); hack.structs = array; @@ -167,13 +167,13 @@ public void WriteArray(T[,] array, long count) { if (count < 1) return; #if NET6_0_OR_GREATER - var sizeOfT = Marshal.SizeOf(typeof(T)); + var sizeOfT = Unsafe.SizeOf(); var byteSpan = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(array), (int)count * sizeOfT); base.Write(byteSpan); #else unsafe { - var sizeOfT = Marshal.SizeOf(typeof(T)); + var sizeOfT = Unsafe.SizeOf(); var hack = new ByteArrayUnion(); hack.structs = array; @@ -208,13 +208,13 @@ public void WriteArray(T[, ,] array, long count) { if (count < 1) return; #if NET6_0_OR_GREATER - var sizeOfT = Marshal.SizeOf(typeof(T)); + var sizeOfT = Unsafe.SizeOf(); var byteSpan = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(array), (int)count * sizeOfT); base.Write(byteSpan); #else unsafe { - var sizeOfT = Marshal.SizeOf(typeof(T)); + var sizeOfT = Unsafe.SizeOf(); var hack = new ByteArrayUnion(); hack.structs = array; From edc96a615f629e3979ccf4c3afa2f53d2aa2b803 Mon Sep 17 00:00:00 2001 From: Christian Luksch Date: Mon, 13 May 2024 23:53:06 +0200 Subject: [PATCH 44/45] added StreamCodeReader benchmark --- .config/dotnet-tools.json | 2 +- src/Aardvark.Base.IO/StreamCodeReader.cs | 5 +- src/Aardvark.Base.IO/StreamCodeWriter.cs | 8 +- .../Aardvark.Base.Benchmarks.csproj | 1 + src/Tests/Aardvark.Base.Benchmarks/Program.cs | 5 +- .../StreamWriterReader.cs | 74 +++++++++++++++++++ 6 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 src/Tests/Aardvark.Base.Benchmarks/StreamWriterReader.cs diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 4c449007..673835b8 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "paket": { - "version": "7.2.1", + "version": "8.0.3", "commands": [ "paket" ] diff --git a/src/Aardvark.Base.IO/StreamCodeReader.cs b/src/Aardvark.Base.IO/StreamCodeReader.cs index ce07af4a..f65ecf41 100644 --- a/src/Aardvark.Base.IO/StreamCodeReader.cs +++ b/src/Aardvark.Base.IO/StreamCodeReader.cs @@ -10,8 +10,6 @@ namespace Aardvark.Base.Coder { public partial class StreamCodeReader : BinaryReader { - private const int c_bufferSize = 262144; - // private byte[] m_buffer = new byte[c_bufferSize]; private byte[] m_guidBuffer = new byte[16]; // own buffer since creator requires 16-byte length #region Constructors @@ -101,6 +99,7 @@ public long ReadArray(byte[] array, long index, long count) return (long)Read(array, (int)index, (int)count); } +#if !NET6_0_OR_GREATER [StructLayout(LayoutKind.Explicit)] struct ByteArrayUnion { @@ -119,6 +118,7 @@ private static IntPtr GetTypeIdUncached() where T : struct gcHandle.Free(); return typeId; } +#endif public long ReadArray(T[] array, long index, long count) where T : struct @@ -307,7 +307,6 @@ public int ReadList(List buffer, int index, int count) public override void Close() { base.Close(); - // m_buffer = null; m_guidBuffer = null; } diff --git a/src/Aardvark.Base.IO/StreamCodeWriter.cs b/src/Aardvark.Base.IO/StreamCodeWriter.cs index 6a765d9a..0d06ff34 100644 --- a/src/Aardvark.Base.IO/StreamCodeWriter.cs +++ b/src/Aardvark.Base.IO/StreamCodeWriter.cs @@ -10,8 +10,10 @@ namespace Aardvark.Base.Coder { public partial class StreamCodeWriter : BinaryWriter { +#if !NET6_0_OR_GREATER private const int c_bufferSize = 262144; private byte[] m_buffer = new byte[c_bufferSize]; +#endif #region Constructors @@ -106,6 +108,7 @@ public void Write(CameraIntrinsics c) #region Write Arrays and Lists +#if !NET6_0_OR_GREATER [StructLayout(LayoutKind.Explicit)] struct ByteArrayUnion { @@ -114,6 +117,7 @@ struct ByteArrayUnion [FieldOffset(0)] public Array structs; } +#endif public void WriteArray(byte[] array, long index, long count) { @@ -259,9 +263,11 @@ public void WriteList(List buffer, int index, int count) public override void Close() { base.Close(); +#if! NET6_0_OR_GREATER m_buffer = null; +#endif } - #endregion +#endregion } } diff --git a/src/Tests/Aardvark.Base.Benchmarks/Aardvark.Base.Benchmarks.csproj b/src/Tests/Aardvark.Base.Benchmarks/Aardvark.Base.Benchmarks.csproj index 9038b6aa..ac034b9c 100644 --- a/src/Tests/Aardvark.Base.Benchmarks/Aardvark.Base.Benchmarks.csproj +++ b/src/Tests/Aardvark.Base.Benchmarks/Aardvark.Base.Benchmarks.csproj @@ -27,6 +27,7 @@ + diff --git a/src/Tests/Aardvark.Base.Benchmarks/Program.cs b/src/Tests/Aardvark.Base.Benchmarks/Program.cs index a1fb1e23..70ed7aa1 100644 --- a/src/Tests/Aardvark.Base.Benchmarks/Program.cs +++ b/src/Tests/Aardvark.Base.Benchmarks/Program.cs @@ -1,6 +1,5 @@ using BenchmarkDotNet.Running; using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Jobs; namespace Aardvark.Base.Benchmarks { @@ -9,7 +8,9 @@ public class Program public static void Main(string[] args) { var cfg = ManualConfig.Create(DefaultConfig.Instance).WithOptions(ConfigOptions.DisableOptimizationsValidator); - BenchmarkSwitcher.FromAssembly(typeof(IntegerPowerFloat).Assembly).Run(args, cfg); + //BenchmarkSwitcher.FromAssembly(typeof(IntegerPowerFloat).Assembly).Run(args, cfg); + + BenchmarkRunner.Run(cfg); //BenchmarkRunner.Run(); //BenchmarkRunner.Run i); + _writer = new StreamCodeWriter(new MemoryStream(_data.Length * 4)); + + var tmpWriter = new StreamCodeWriter(new MemoryStream(_data.Length * 4)); + tmpWriter.WriteArray(_data, 0, _data.Length); + _reader = new StreamCodeReader(tmpWriter.BaseStream); + } + + [Benchmark] + public void WriteArray() + { + _writer.BaseStream.Position = 0; + _writer.WriteArray(_data, 0, _data.Length); + } + + [Benchmark] + public void ReadArray() + { + _reader.BaseStream.Position = 0; + _reader.ReadArray(_data, 0, _data.Length); + } + } +} From 9871a422c757b2786cfa7b06bf3c481161fe8313 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 14 May 2024 15:16:41 +0200 Subject: [PATCH 45/45] [FSharp] Optimize dictionary functions --- .../Utilities/Interop/Dictionary.fs | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs b/src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs index 5c52bf26..ebee3d53 100644 --- a/src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs +++ b/src/Aardvark.Base.FSharp/Utilities/Interop/Dictionary.fs @@ -25,13 +25,13 @@ module Dictionary = d.Clear() let inline map (f : 'k -> 'a -> 'b) (d : Dictionary<'k, 'a>) = - let result = Dictionary() + let result = Dictionary(capacity = d.Count) for (KeyValue(k,v)) in d do result.[k] <- f k v result let inline mapKeys (f : 'k -> 'a -> 'b) (d : Dictionary<'k, 'a>) = - let result = Dictionary() + let result = Dictionary(capacity = d.Count) for (KeyValue(k,v)) in d do result.[f k v] <- v result @@ -75,10 +75,16 @@ module Dictionary = ofSeqV elements let inline ofArray (elements : ('k * 'v)[]) = - ofSeq elements + let result = Dictionary(capacity = elements.Length) + for (k,v) in elements do + result.[k] <- v + result let inline ofArrayV (elements : (struct('k * 'v))[]) = - ofSeqV elements + let result = Dictionary(capacity = elements.Length) + for (k,v) in elements do + result.[k] <- v + result let inline ofMap (elements : Map<'k, 'v>) = elements |> Map.toSeq |> ofSeq @@ -145,13 +151,13 @@ module Dict = #endif let inline map (f : 'k -> 'a -> 'b) (d : Dict<'k, 'a>) = - let result = Dict() + let result = Dict(initialCapacity = d.Count) for (KeyValue(k,v)) in d do result.[k] <- f k v result let inline mapKeys (f : 'k -> 'a -> 'b) (d : Dict<'k, 'a>) = - let result = Dict() + let result = Dict(initialCapacity = d.Count) for (KeyValue(k,v)) in d do result.[f k v] <- v result @@ -195,10 +201,16 @@ module Dict = ofSeqV elements let inline ofArray (elements : ('k * 'v)[]) = - ofSeq elements + let result = Dict(initialCapacity = elements.Length) + for (k,v) in elements do + result.[k] <- v + result let inline ofArrayV (elements : struct('k * 'v)[]) = - ofSeqV elements + let result = Dict(initialCapacity = elements.Length) + for (k,v) in elements do + result.[k] <- v + result let inline ofMap (elements : Map<'k, 'v>) = elements |> Map.toSeq |> ofSeq @@ -242,13 +254,13 @@ module SymDict = d.Clear() let inline map (f : Symbol -> 'a -> 'b) (d : SymbolDict<'a>) = - let result = SymbolDict() + let result = SymbolDict(initialCapacity = d.Count) for (KeyValue(k,v)) in d do result.[k] <- f k v result let inline mapKeys (f : Symbol -> 'a -> Symbol) (d : SymbolDict<'a>) = - let result = SymbolDict() + let result = SymbolDict(initialCapacity = d.Count) for (KeyValue(k,v)) in d do result.[f k v] <- v result @@ -276,13 +288,13 @@ module SymDict = let inline ofSeq (elements : seq) = let result = SymbolDict() for (k,v) in elements do - result.Add(k,v) + result.[k] <- v result let inline ofSeqV (elements : seq) = let result = SymbolDict() for (k,v) in elements do - result.Add(k,v) + result.[k] <- v result let inline ofList (elements : list) = @@ -292,10 +304,16 @@ module SymDict = ofSeqV elements let inline ofArray (elements : (Symbol * 'v)[]) = - ofSeq elements + let result = SymbolDict(initialCapacity = elements.Length) + for (k,v) in elements do + result.[k] <- v + result let inline ofArrayV (elements : (struct(Symbol * 'v))[]) = - ofSeqV elements + let result = SymbolDict(initialCapacity = elements.Length) + for (k,v) in elements do + result.[k] <- v + result let inline ofMap (elements : Map) = elements |> Map.toSeq |> ofSeq