diff --git a/BrightData.UnitTests/FixedSizeArrayTests.cs b/BrightData.UnitTests/FixedSizeArrayTests.cs index 7e0fec79..2f490fbc 100644 --- a/BrightData.UnitTests/FixedSizeArrayTests.cs +++ b/BrightData.UnitTests/FixedSizeArrayTests.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using BrightData.Types; +using BrightData.Types.Graph; using FluentAssertions; using Xunit; diff --git a/BrightData.UnitTests/SortedArrayTests.cs b/BrightData.UnitTests/SortedArrayTests.cs new file mode 100644 index 00000000..8a9f0198 --- /dev/null +++ b/BrightData.UnitTests/SortedArrayTests.cs @@ -0,0 +1,41 @@ +using BrightData.Types.Graph; +using BrightData.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FluentAssertions; +using Xunit; + +namespace BrightData.UnitTests +{ + public class SortedArrayTests + { + [Fact] + public void TestIndexedSortedArray() + { + var array = new IndexedSortedArray(4); + for (var i = 0U; i < 4; i++) + array.Add(new(4-i)); + array.Size.Should().Be(4); + array[0].Index.Should().Be(1); + array[3].Index.Should().Be(4); + array.TryFind(1, out var index).Should().BeTrue(); + index!.Value.Index.Should().Be(1); + } + + [Fact] + public void TestSortedArray() + { + var array = new SortedArray(4); + for (var i = 0U; i < 4; i++) + array.Add(i, 4-i); + array.Size.Should().Be(4); + array[0].Weight.Should().Be(1); + array[3].Weight.Should().Be(4); + array.TryFind(1, out var value).Should().BeTrue(); + value.Should().Be(3); + } + } +} diff --git a/BrightData.UnitTests/VectorSetTests.cs b/BrightData.UnitTests/VectorSetTests.cs index 35a5dcb2..6b78d4b7 100644 --- a/BrightData.UnitTests/VectorSetTests.cs +++ b/BrightData.UnitTests/VectorSetTests.cs @@ -2,8 +2,8 @@ using BrightData.UnitTests.Helper; using System.Linq; using BrightData.LinearAlgebra.VectorIndexing; -using BrightData.LinearAlgebra.VectorIndexing.Helper; using BrightData.Types; +using BrightData.Types.Graph; using FluentAssertions; using Xunit; using Xunit.Abstractions; @@ -67,7 +67,7 @@ public void Closest() [Fact] public void TestVectorGraphNode() { - var node = new IndexedFixedSizeGraphNode>(1); + var node = new FixedSizeWeightedGraphNode>(new(1)); node.Index.Should().Be(1); node.NeighbourIndices.Length.Should().Be(0); diff --git a/BrightData/Interfaces.cs b/BrightData/Interfaces.cs index 5cd82836..9b5fa753 100644 --- a/BrightData/Interfaces.cs +++ b/BrightData/Interfaces.cs @@ -616,7 +616,7 @@ public interface IFixedSizeSortedArray : IHaveSize /// Returns a value and weight /// /// Index to return - (V Value, W Weight) this[byte index] { get; } + (V Value, W Weight) this[uint index] { get; } /// /// Enumerates the values and weights @@ -652,7 +652,7 @@ public interface IWeightedGraphNode : IGraphNode { public T Value { get; } - bool AddNeighbour(uint index, W weight); + bool TryAddNeighbour(uint index, W weight); IEnumerable<(uint Index, W Weight)> WeightedNeighbours { get; } } @@ -663,14 +663,13 @@ public interface ICalculateNodeWeights W GetWeight(uint fromIndex, uint toIndex); } - public interface IWeightedGraph : IHaveSize + public interface IWeightedGraph : IHaveSize where T: IHaveSingleIndex where W : unmanaged, INumber, IMinMaxValue { - void Add(T value); - void Add(T value, ReadOnlySpan<(uint Index, W Weight)> neighbours); + T Find(uint nodeIndex); - T Get(uint index); + T this[uint nodePosition] { get; } RAT Search(uint q, uint entryPoint, ICalculateNodeWeights distanceCalculator) where RAT : struct, IFixedSizeSortedArray @@ -681,4 +680,12 @@ RAT Search(uint q, uint entryPoint, ICalculateNodeWeights distanceC bool AddNeighbour(uint nodeIndex, uint neighbourIndex, W weight); } + + public interface IWeightedDynamicGraph : IWeightedGraph + where T: IHaveSingleIndex + where W : unmanaged, INumber, IMinMaxValue + { + void Add(T value); + void Add(T value, ReadOnlySpan<(uint Index, W Weight)> neighbours); + } } diff --git a/BrightData/LinearAlgebra/VectorIndexing/Helper/IndexedFixedSizeGraphNode.cs b/BrightData/LinearAlgebra/VectorIndexing/Helper/IndexedFixedSizeGraphNode.cs deleted file mode 100644 index 065a5a22..00000000 --- a/BrightData/LinearAlgebra/VectorIndexing/Helper/IndexedFixedSizeGraphNode.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Numerics; - -namespace BrightData.LinearAlgebra.VectorIndexing.Helper -{ - /// - /// Fixed size indexed graph node that maintains weighted list of neighbours as a max heap - /// - /// Weight type - /// Array type - /// - public record struct IndexedFixedSizeGraphNode(uint Index) - where T : unmanaged, INumber, IMinMaxValue - where AT: struct, IFixedSizeSortedArray - { - AT _neighbours = new(); - - /// - /// Max number of neighbours - /// - public int MaxNeighbours => _neighbours.MaxSize; - - /// - /// Current number of neighbours - /// - public byte NeighbourCount => _neighbours.Size; - - /// - /// The smallest neighbour weight - /// - public T MinWeight => _neighbours.MinWeight; - - /// - /// The largest neighbour weight - /// - public T MaxWeight => _neighbours.MaxWeight; - - /// - /// The index of the neighbour with the smallest weight - /// - public uint MinNeighbourIndex => _neighbours.MinValue; - - /// - /// The index of the neighbour with the largest weight - /// - public uint MaxNeighbourIndex => _neighbours.MaxValue; - - /// - /// Tries to add a new neighbour - will succeed if there aren't already max neighbours with a smaller weight - /// - /// - /// - /// - /// - public bool TryAddNeighbour(uint neighbourIndex, T neighbourWeight, bool enforceUnique = true) => _neighbours.TryAdd(neighbourIndex, neighbourWeight, enforceUnique); - - /// - /// Sorted list of neighbour indices - /// - public ReadOnlySpan NeighbourIndices => _neighbours.Values; - - /// - /// Sorted list of neighbour weights - /// - public ReadOnlySpan NeighbourWeights => _neighbours.Weights; - - /// - /// Returns a neighbour weight - /// - /// - public (uint NeighbourIndex, T NeighbourWeight) this[byte index] => _neighbours[index]; - - /// - /// Enumerates the weighted neighbours - /// - public IEnumerable<(uint NeighbourIndex, T NeighbourWeight)> WeightedNeighbours => _neighbours.Elements; - } -} diff --git a/BrightData/LinearAlgebra/VectorIndexing/Helper/VectorGraph.cs b/BrightData/LinearAlgebra/VectorIndexing/Helper/VectorGraph.cs deleted file mode 100644 index 48defe3b..00000000 --- a/BrightData/LinearAlgebra/VectorIndexing/Helper/VectorGraph.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using BrightData.Types; -using CommunityToolkit.HighPerformance; - -namespace BrightData.LinearAlgebra.VectorIndexing.Helper -{ - /// - /// Creates a graph of vectors with a fixed size set of neighbours - /// - /// - public class VectorGraph : IHaveSize - where T : unmanaged, IBinaryFloatingPointIeee754, IMinMaxValue - { - readonly IndexedFixedSizeGraphNode>[] _nodes; - - /// - /// Creates a vector graph from an array of nodes - /// - /// - public VectorGraph(IndexedFixedSizeGraphNode>[] nodes) - { - _nodes = nodes; - } - - /// - /// Number of nodes in the graph - /// - public uint Size => (uint)_nodes.Length; - - /// - /// Gets the neighbours for a node, sorted by distance - /// - /// - /// - public ReadOnlySpan GetNeighbours(uint vectorIndex) => _nodes[vectorIndex].NeighbourIndices; - - /// - /// Gets the weights for the node's neighbours - /// - /// - /// - public ReadOnlySpan GetNeighbourWeights(uint vectorIndex) => _nodes[vectorIndex].NeighbourWeights; - - /// - /// Enumerates the neighbour indices and their weights in ascending order - /// - /// - /// - public IEnumerable<(uint NeighbourIndex, T NeighbourWeight)> GetWeightedNeighbours(uint vectorIndex) => _nodes[vectorIndex].WeightedNeighbours; - - /// - /// Creates - /// - /// - /// - /// - /// - /// - [SkipLocalsInit] - public static unsafe VectorGraph Build( - IStoreVectors vectors, - DistanceMetric distanceMetric, - bool shortCircuitIfNodeNeighboursAreFull = true, - Action? onNode = null) - { - var size = vectors.Size; - var distance = size <= 1024 - ? stackalloc T[(int)size] - : new T[size]; - - var ret = GC.AllocateUninitializedArray>>((int)size); - for (var i = 0U; i < size; i++) - ret[i] = new(i); - - for (var i = 0U; i < size; i++) - { - if (shortCircuitIfNodeNeighboursAreFull && ret[i].NeighbourCount == FixedSizeSortedAscending8Array.MaxSize) - continue; - - // find the distance between this node and each of its neighbours - fixed (T* dest = distance) - { - var destPtr = dest; - var currentIndex = i; - vectors.ForEach((x, j) => - { - if(currentIndex != j) - destPtr[j] = T.Abs(x.FindDistance(vectors[currentIndex], distanceMetric)); - }); - } - - // find top N closest neighbours - var maxHeap = new IndexedFixedSizeGraphNode>(); - for (var j = 0; j < size; j++) { - if (i == j) - continue; - var d = distance[j]; - maxHeap.TryAddNeighbour((uint)j, d); - } - - // connect the closest nodes - foreach (var (index, d) in maxHeap.WeightedNeighbours) - { - ret[index].TryAddNeighbour(i, d); - ret[i].TryAddNeighbour(index, d); - } - onNode?.Invoke(i); - } - - return new(ret); - } - - /// - /// Writes the graph to disk - /// - /// - public async Task WriteToDisk(string filePath) - { - using var fileHandle = File.OpenHandle(filePath, FileMode.Create, FileAccess.Write, FileShare.None, FileOptions.Asynchronous); - await RandomAccess.WriteAsync(fileHandle, _nodes.AsMemory().AsBytes(), 0); - } - - /// - /// Loads a vector graph from disk - /// - /// - /// - public static async Task> LoadFromDisk(string filePath) - { - using var fileHandle = File.OpenHandle(filePath); - var ret = GC.AllocateUninitializedArray>>((int)(RandomAccess.GetLength(fileHandle) / Unsafe.SizeOf>>())); - await RandomAccess.ReadAsync(fileHandle, ret.AsMemory().AsBytes(), 0); - return new(ret); - } - } -} diff --git a/BrightData/Types/FixedSizeSortedArray/FixedSizeSortedArrayTemplate.cs b/BrightData/Types/FixedSizeSortedArray/FixedSizeSortedArrayTemplate.cs index 313302e1..7ea2f0d8 100644 --- a/BrightData/Types/FixedSizeSortedArray/FixedSizeSortedArrayTemplate.cs +++ b/BrightData/Types/FixedSizeSortedArray/FixedSizeSortedArrayTemplate.cs @@ -77,12 +77,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -205,12 +205,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -333,12 +333,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -461,12 +461,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -589,12 +589,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -717,12 +717,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -845,12 +845,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -973,12 +973,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -1101,12 +1101,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -1229,12 +1229,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -1357,12 +1357,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -1485,12 +1485,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -1613,12 +1613,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -1741,12 +1741,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -1869,12 +1869,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -1997,12 +1997,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -2125,12 +2125,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -2253,12 +2253,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -2381,12 +2381,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -2509,12 +2509,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -2637,12 +2637,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -2765,12 +2765,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -2893,12 +2893,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -3021,12 +3021,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -3149,12 +3149,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -3277,12 +3277,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -3405,12 +3405,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -3533,12 +3533,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -3661,12 +3661,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -3789,12 +3789,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -3917,12 +3917,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -4045,12 +4045,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -4173,12 +4173,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -4301,12 +4301,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -4429,12 +4429,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -4557,12 +4557,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -4685,12 +4685,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -4813,12 +4813,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -4941,12 +4941,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -5069,12 +5069,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -5197,12 +5197,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -5325,12 +5325,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -5453,12 +5453,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -5581,12 +5581,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -5709,12 +5709,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -5837,12 +5837,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -5965,12 +5965,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -6093,12 +6093,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -6221,12 +6221,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -6349,12 +6349,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -6477,12 +6477,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -6605,12 +6605,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -6733,12 +6733,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -6861,12 +6861,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -6989,12 +6989,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -7117,12 +7117,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -7245,12 +7245,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -7373,12 +7373,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -7501,12 +7501,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -7629,12 +7629,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -7757,12 +7757,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -7885,12 +7885,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -8013,12 +8013,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -8141,12 +8141,12 @@ internal struct WeightArray /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } diff --git a/BrightData/Types/FixedSizeSortedArray/FixedSizeSortedArrayTemplate.tt b/BrightData/Types/FixedSizeSortedArray/FixedSizeSortedArrayTemplate.tt index 9cfee075..3aaa244b 100644 --- a/BrightData/Types/FixedSizeSortedArray/FixedSizeSortedArrayTemplate.tt +++ b/BrightData/Types/FixedSizeSortedArray/FixedSizeSortedArrayTemplate.tt @@ -84,12 +84,12 @@ namespace BrightData.Types /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } @@ -212,12 +212,12 @@ namespace BrightData.Types /// Returns a value and weight /// /// Index to return - public readonly (V Value, W Weight) this[byte index] + public readonly (V Value, W Weight) this[uint index] { get { if (index < Size) - return (Values[index], Weights[index]); + return (Values[(int)index], Weights[(int)index]); throw new ArgumentOutOfRangeException(); } } diff --git a/BrightData/Types/Graph/FixedSizeWeightedDynamicGraph.cs b/BrightData/Types/Graph/FixedSizeWeightedDynamicGraph.cs new file mode 100644 index 00000000..2b27fa5d --- /dev/null +++ b/BrightData/Types/Graph/FixedSizeWeightedDynamicGraph.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using BrightData.Types.Helper; + +namespace BrightData.Types.Graph +{ + /// + /// A fixed size weighted graph + /// + /// Type to store in each node + /// Type to describe weights + /// Array type (fixed size) + public class FixedSizeWeightedDynamicGraph : IWeightedDynamicGraph + where T : unmanaged, IHaveSingleIndex + where W : unmanaged, INumber, IMinMaxValue + where AT : unmanaged, IFixedSizeSortedArray + { + readonly IndexedSortedArray> _nodes = new(); + + /// + public void Add(T value) + { + _nodes.Add(new FixedSizeWeightedGraphNode(value)); + } + + /// + public void Add(T value, ReadOnlySpan<(uint Index, W Weight)> neighbours) + { + var node = new FixedSizeWeightedGraphNode(value); + foreach (var (index, weight) in neighbours) + node.TryAddNeighbour(index, weight); + _nodes.Add(node); + } + + /// + public T Find(uint nodeIndex) + { + ref var node = ref _nodes.Find(nodeIndex); + if (!Unsafe.IsNullRef(ref node)) + return node.Value; + throw new ArgumentException($"Node with index {nodeIndex} was not found"); + } + + /// + public T this[uint nodePosition] => _nodes[nodePosition].Value; + + /// + public ReadOnlySpan GetNeighbours(uint nodeIndex) + { + ref var node = ref _nodes.Find(nodeIndex); + if (!Unsafe.IsNullRef(ref node)) + return node.NeighbourSpan; + return ReadOnlySpan.Empty; + } + + /// + public bool AddNeighbour(uint nodeIndex, uint neighbourIndex, W weight) + { + ref var node = ref _nodes.Find(nodeIndex); + if (!Unsafe.IsNullRef(ref node)) { + return node.TryAddNeighbour(neighbourIndex, weight); + } + + return false; + } + + /// + public uint Size => _nodes.Size; + + /// + public RAT Search(uint q, uint entryPoint, ICalculateNodeWeights distanceCalculator) + where RAT : struct, IFixedSizeSortedArray + where CAT : struct, IFixedSizeSortedArray + { + return WeightedGraphHelper.SearchFixedSize(q, entryPoint, distanceCalculator, GetNeighbours); + } + } +} diff --git a/BrightData/Types/Graph/FixedSizeWeightedGraph.cs b/BrightData/Types/Graph/FixedSizeWeightedGraph.cs index 619e0221..df2a8a21 100644 --- a/BrightData/Types/Graph/FixedSizeWeightedGraph.cs +++ b/BrightData/Types/Graph/FixedSizeWeightedGraph.cs @@ -1,104 +1,167 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.IO; using System.Numerics; using System.Runtime.CompilerServices; -using System.Text; +using CommunityToolkit.HighPerformance; using System.Threading.Tasks; +using BrightData.Types.Helper; namespace BrightData.Types.Graph { - /// - /// A fixed size graph weighted graph - /// - /// - /// - /// - public class FixedSizeWeightedGraph : IWeightedGraph - where T : unmanaged, IHaveSingleIndex - where W : unmanaged, INumber, IMinMaxValue + public class FixedSizeWeightedGraph : IWeightedGraph + where W : unmanaged, IBinaryFloatingPointIeee754, IMinMaxValue where AT : unmanaged, IFixedSizeSortedArray - { - readonly IndexedSortedArray> _nodes = new(); + readonly FixedSizeWeightedGraphNode[] _nodes; - /// - public void Add(T value) + /// + /// Creates a vector graph from an array of nodes + /// + /// + public FixedSizeWeightedGraph(FixedSizeWeightedGraphNode[] nodes) { - _nodes.Add(new FixedSizeWeightedGraphNode(value)); + _nodes = nodes; } - /// - public void Add(T value, ReadOnlySpan<(uint Index, W Weight)> neighbours) - { - var node = new FixedSizeWeightedGraphNode(value); - foreach (var (index, weight) in neighbours) - node.AddNeighbour(index, weight); - _nodes.Add(node); - } + /// + /// Number of nodes in the graph + /// + public uint Size => (uint)_nodes.Length; /// - public T Get(uint nodeIndex) + public RAT Search(uint q, uint entryPoint, ICalculateNodeWeights distanceCalculator) where RAT : struct, IFixedSizeSortedArray where CAT : struct, IFixedSizeSortedArray { - ref var node = ref _nodes.Get(nodeIndex); - if (!Unsafe.IsNullRef(ref node)) - return node.Value; - throw new ArgumentException($"Node with index {nodeIndex} was not found"); + return WeightedGraphHelper.SearchFixedSize(q, entryPoint, distanceCalculator, GetNeighbours); } - /// - public ReadOnlySpan GetNeighbours(uint nodeIndex) - { - ref var node = ref _nodes.Get(nodeIndex); - if (!Unsafe.IsNullRef(ref node)) - return node.NeighbourSpan; - return ReadOnlySpan.Empty; - } + /// + /// Gets the neighbours for a node, sorted by distance + /// + /// + /// + public ReadOnlySpan GetNeighbours(uint vectorIndex) => _nodes[vectorIndex].NeighbourIndices; + + /// + /// Gets the weights for the node's neighbours + /// + /// + /// + public ReadOnlySpan GetNeighbourWeights(uint vectorIndex) => _nodes[vectorIndex].NeighbourWeights; + + /// + /// Enumerates the neighbour indices and their weights in ascending order + /// + /// + /// + public IEnumerable<(uint NeighbourIndex, W NeighbourWeight)> GetWeightedNeighbours(uint vectorIndex) => _nodes[vectorIndex].WeightedNeighbours; /// public bool AddNeighbour(uint nodeIndex, uint neighbourIndex, W weight) { - ref var node = ref _nodes.Get(nodeIndex); + ref var node = ref _nodes[nodeIndex]; if (!Unsafe.IsNullRef(ref node)) { - return node.AddNeighbour(neighbourIndex, weight); + return node.TryAddNeighbour(neighbourIndex, weight); } return false; } /// - public uint Size => _nodes.Size; + public GraphNodeIndex Find(uint nodeIndex) + { + ref var node = ref _nodes[nodeIndex]; + if (!Unsafe.IsNullRef(ref node)) + return node.Value; + throw new ArgumentException($"Node with index {nodeIndex} was not found"); + } /// - public RAT Search(uint q, uint entryPoint, ICalculateNodeWeights distanceCalculator) - where RAT : struct, IFixedSizeSortedArray - where CAT : struct, IFixedSizeSortedArray + public GraphNodeIndex this[uint nodePosition] => _nodes[nodePosition].Value; + + /// + /// Creates + /// + /// + /// + /// + /// + /// + [SkipLocalsInit] + public static unsafe FixedSizeWeightedGraph Build( + IStoreVectors vectors, + DistanceMetric distanceMetric, + bool shortCircuitIfNodeNeighboursAreFull = true, + Action? onNode = null) { - var visited = new HashSet { entryPoint }; - var candidates = new CAT(); - var distanceEQ = distanceCalculator.GetWeight(q, entryPoint); - candidates.TryAdd(entryPoint, distanceEQ); - var ret = new RAT(); - ret.TryAdd(entryPoint, distanceEQ); - - while (candidates.Size > 0) { - var c = candidates.RemoveAt(0); - var f = ret.MaxValue; - if (distanceCalculator.GetWeight(c, q) > distanceCalculator.GetWeight(f, q)) - break; - - foreach (var neighbour in GetNeighbours(c)) { - if(!visited.Add(neighbour)) + var size = vectors.Size; + var distance = size <= 1024 + ? stackalloc W[(int)size] + : new W[size]; + + var ret = GC.AllocateUninitializedArray>((int)size); + for (var i = 0U; i < size; i++) + ret[i] = new(new(i)); + + for (var i = 0U; i < size; i++) + { + if (shortCircuitIfNodeNeighboursAreFull && ret[i].NeighbourCount == FixedSizeSortedAscending8Array.MaxSize) + continue; + + // find the distance between this node and each of its neighbours + fixed (W* dest = distance) + { + var destPtr = dest; + var currentIndex = i; + vectors.ForEach((x, j) => + { + if(currentIndex != j) + destPtr[j] = W.Abs(x.FindDistance(vectors[currentIndex], distanceMetric)); + }); + } + + // find top N closest neighbours + var maxHeap = new FixedSizeWeightedGraphNode>(); + for (var j = 0; j < size; j++) { + if (i == j) continue; + var d = distance[j]; + maxHeap.TryAddNeighbour((uint)j, d); + } - f = ret.MaxValue; - if ((distanceEQ = distanceCalculator.GetWeight(neighbour, q)) < distanceCalculator.GetWeight(f, q)) { - candidates.TryAdd(neighbour, distanceEQ); - ret.TryAdd(neighbour, distanceEQ); - } + // connect the closest nodes + foreach (var (index, d) in maxHeap.WeightedNeighbours) + { + ret[index].TryAddNeighbour(i, d); + ret[i].TryAddNeighbour(index, d); } + onNode?.Invoke(i); } - return ret; + + return new(ret); + } + + /// + /// Writes the graph to disk + /// + /// + public async Task WriteToDisk(string filePath) + { + using var fileHandle = File.OpenHandle(filePath, FileMode.Create, FileAccess.Write, FileShare.None, FileOptions.Asynchronous); + await RandomAccess.WriteAsync(fileHandle, _nodes.AsMemory().AsBytes(), 0); + } + + /// + /// Loads a vector graph from disk + /// + /// + /// + public static async Task> LoadFromDisk(string filePath) + { + using var fileHandle = File.OpenHandle(filePath); + var ret = GC.AllocateUninitializedArray>((int)(RandomAccess.GetLength(fileHandle) / Unsafe.SizeOf>())); + await RandomAccess.ReadAsync(fileHandle, ret.AsMemory().AsBytes(), 0); + return new(ret); } } } diff --git a/BrightData/Types/Graph/FixedSizeWeightedGraphNode.cs b/BrightData/Types/Graph/FixedSizeWeightedGraphNode.cs index c03b561e..4036de27 100644 --- a/BrightData/Types/Graph/FixedSizeWeightedGraphNode.cs +++ b/BrightData/Types/Graph/FixedSizeWeightedGraphNode.cs @@ -17,18 +17,64 @@ public record struct FixedSizeWeightedGraphNode(T Value) : IWeightedGr /// public uint Index => Value.Index; + /// + /// Max number of neighbours + /// + public int MaxNeighbours => _neighbours.MaxSize; + + /// + /// Current number of neighbours + /// + public byte NeighbourCount => _neighbours.Size; + + /// + /// The smallest neighbour weight + /// + public W MinWeight => _neighbours.MinWeight; + + /// + /// The largest neighbour weight + /// + public W MaxWeight => _neighbours.MaxWeight; + + /// + /// The index of the neighbour with the smallest weight + /// + public uint MinNeighbourIndex => _neighbours.MinValue; + + /// + /// The index of the neighbour with the largest weight + /// + public uint MaxNeighbourIndex => _neighbours.MaxValue; + + /// + /// Sorted list of neighbour indices + /// + public ReadOnlySpan NeighbourIndices => _neighbours.Values; + + /// + /// Sorted list of neighbour weights + /// + public ReadOnlySpan NeighbourWeights => _neighbours.Weights; + /// /// Tries to add a new neighbour /// /// Index of neighbour /// Neighbour weight - public bool AddNeighbour(uint index, W weight) + public bool TryAddNeighbour(uint index, W weight) { if(index != Value.Index) return _neighbours.TryAdd(index, weight); return false; } + /// + /// Returns a neighbour weight + /// + /// Position of neighbour to return + public (uint NeighbourIndex, W NeighbourWeight) this[uint position] => _neighbours[position]; + /// public IEnumerable<(uint Index, W Weight)> WeightedNeighbours => _neighbours.Elements; diff --git a/BrightData/Types/Graph/GraphIndex.cs b/BrightData/Types/Graph/GraphNodeIndex.cs similarity index 100% rename from BrightData/Types/Graph/GraphIndex.cs rename to BrightData/Types/Graph/GraphNodeIndex.cs diff --git a/BrightData/Types/Graph/HierarchicalNavigationSmallWorldGraph.cs b/BrightData/Types/Graph/HierarchicalNavigationSmallWorldGraph.cs index 425763d6..efbca1e3 100644 --- a/BrightData/Types/Graph/HierarchicalNavigationSmallWorldGraph.cs +++ b/BrightData/Types/Graph/HierarchicalNavigationSmallWorldGraph.cs @@ -19,8 +19,8 @@ public record struct NodeIndex(T Value, uint LayerIndex) : IHaveSingleIndex public uint Index => Value.Index; } readonly IContinuousDistribution _distribution = context.CreateExponentialDistribution(W.CreateSaturating(maxLayers)); - readonly IWeightedGraph[] _layers = Enumerable.Range(0, maxLayers) - .Select(i => (IWeightedGraph)(i == 0 ? new FixedSizeWeightedGraph() : new FixedSizeWeightedGraph())) + readonly IWeightedDynamicGraph[] _layers = Enumerable.Range(0, maxLayers) + .Select(i => (IWeightedDynamicGraph)(i == 0 ? new FixedSizeWeightedDynamicGraph() : new FixedSizeWeightedDynamicGraph())) .ToArray() ; NodeIndex? _entryPoint = null; @@ -40,7 +40,7 @@ public void Add(IEnumerable values, ICalculateNodeWeights distanceCalculat for (var i = entryPointLevel.Value; i > level; i--) { var layer = _layers[i]; var w = layer.Search, AT>(value.Index, entryPoint.Value.Index, distanceCalculator); - entryPoint = layer.Get(w.MinValue); + entryPoint = layer.Find(w.MinValue); } } @@ -62,7 +62,7 @@ public void Add(IEnumerable values, ICalculateNodeWeights distanceCalculat } if(!entryPointLevel.HasValue || level > entryPointLevel.Value) - _entryPoint = _layers[level].Get(value.Index); + _entryPoint = _layers[level].Find(value.Index); } } @@ -72,7 +72,7 @@ public AT KnnSearch(uint q, ICalculateNodeWeights distanceCalculator) for (var i = (int)entryPoint.LayerIndex; i > 0; i--) { var layer = _layers[i]; var w = layer.Search(q, entryPoint.Index, distanceCalculator); - entryPoint = layer.Get(w.MinValue); + entryPoint = layer.Find(w.MinValue); } return _layers[0].Search(q, entryPoint.Index, distanceCalculator); } diff --git a/BrightData/Types/Helper/SortedArrayHelper.cs b/BrightData/Types/Helper/SortedArrayHelper.cs index 10348215..74b3cf9f 100644 --- a/BrightData/Types/Helper/SortedArrayHelper.cs +++ b/BrightData/Types/Helper/SortedArrayHelper.cs @@ -1,4 +1,5 @@ -using System; +using CommunityToolkit.HighPerformance; +using System; using System.Numerics; namespace BrightData.Types.Helper @@ -6,9 +7,9 @@ namespace BrightData.Types.Helper /// /// Fixed size array helper methods /// - public class SortedArrayHelper + internal static class SortedArrayHelper { - internal static bool InsertIndexed(uint currSize, V value, Span values) + internal static void InsertIndexed(uint currSize, V value, Span values) where V : unmanaged, IHaveSingleIndex { var size = (int)currSize; @@ -19,32 +20,28 @@ internal static bool InsertIndexed(uint currSize, V value, Span values) right = size - 1, insertPosition = size ; - while (left <= right) - { + while (left <= right) { var mid = left + (right - left) / 2; - if (values[mid].Index > index) - { + if (values[mid].Index > index) { insertPosition = mid; right = mid - 1; } - else - { + else { left = mid + 1; } } - if (insertPosition != size) - { + if (insertPosition != size) { // shuffle to make room - for (var i = size - 1; i >= insertPosition; i--) - { - values[i + 1] = values[i]; - } + var vf = values[insertPosition..size]; + vf.CopyTo(values.Slice(insertPosition+1, vf.Length)); + //for (var i = size - 1; i >= insertPosition; i--) { + // values[i + 1] = values[i]; + //} } // insert the item values[insertPosition] = value; - return true; } internal static bool InsertIntoAscending(bool enforceUnique, uint currSize, uint maxSize, V value, W weight, Span values, Span weights) @@ -63,16 +60,13 @@ internal static bool InsertIntoAscending(bool enforceUnique, uint currSize right = size - 1, insertPosition = size ; - while (left <= right) - { + while (left <= right) { var mid = left + (right - left) / 2; - if (weights[mid] > weight) - { + if (weights[mid] > weight) { insertPosition = mid; right = mid - 1; } - else - { + else { // check for an existing weight/value if (enforceUnique && weights[mid] == weight && values[mid].CompareTo(value) == 0) return false; @@ -80,11 +74,9 @@ internal static bool InsertIntoAscending(bool enforceUnique, uint currSize } } - if (enforceUnique) - { + if (enforceUnique) { // check if the same element already exists in the left partition - for (var i = insertPosition - 1; i >= 0; i--) - { + for (var i = insertPosition - 1; i >= 0; i--) { if (weights[i] < weight) break; if (values[i].CompareTo(value) == 0) @@ -92,8 +84,7 @@ internal static bool InsertIntoAscending(bool enforceUnique, uint currSize } // check if the same element already exists in the right partition - for (var i = insertPosition; i < size; i++) - { + for (var i = insertPosition; i < size; i++) { if (weights[i] > weight) break; if (values[i].CompareTo(value) == 0) @@ -101,20 +92,18 @@ internal static bool InsertIntoAscending(bool enforceUnique, uint currSize } } - if (insertPosition == size) - { + if (insertPosition == size) { // there is no room left if (isFull) return false; } - else - { + else { // shuffle to make room - for (var i = size - (isFull ? 2 : 1); i >= insertPosition; i--) - { - values[i + 1] = values[i]; - weights[i + 1] = weights[i]; - } + ShiftRight(isFull, insertPosition, size, values, weights); + //for (var i = size - (isFull ? 2 : 1); i >= insertPosition; i--) { + // values[i + 1] = values[i]; + // weights[i + 1] = weights[i]; + //} } // insert the item @@ -139,16 +128,13 @@ internal static bool InsertIntoDescending(bool enforceUnique, uint currSiz right = size - 1, insertPosition = size ; - while (left <= right) - { + while (left <= right) { var mid = left + (right - left) / 2; - if (weights[mid] < weight) - { + if (weights[mid] < weight) { insertPosition = mid; right = mid - 1; } - else - { + else { // check for an existing weight/value if (enforceUnique && weights[mid] == weight && values[mid].CompareTo(value) == 0) return false; @@ -156,11 +142,9 @@ internal static bool InsertIntoDescending(bool enforceUnique, uint currSiz } } - if (enforceUnique) - { + if (enforceUnique) { // check if the same element already exists in the left partition - for (var i = insertPosition - 1; i >= 0; i--) - { + for (var i = insertPosition - 1; i >= 0; i--) { if (weights[i] > weight) break; if (values[i].CompareTo(value) == 0) @@ -168,8 +152,7 @@ internal static bool InsertIntoDescending(bool enforceUnique, uint currSiz } // check if the same element already exists in the right partition - for (var i = insertPosition; i < size; i++) - { + for (var i = insertPosition; i < size; i++) { if (weights[i] < weight) break; if (values[i].CompareTo(value) == 0) @@ -177,20 +160,18 @@ internal static bool InsertIntoDescending(bool enforceUnique, uint currSiz } } - if (insertPosition == size) - { + if (insertPosition == size) { // there is no room left if (isFull) return false; } - else - { + else { // shuffle to make room - for (var i = size - (isFull ? 2 : 1); i >= insertPosition; i--) - { - values[i + 1] = values[i]; - weights[i + 1] = weights[i]; - } + ShiftRight(isFull, insertPosition, size, values, weights); + //for (var i = size - (isFull ? 2 : 1); i >= insertPosition; i--) { + // values[i + 1] = values[i]; + // weights[i + 1] = weights[i]; + //} } // insert the item @@ -199,6 +180,16 @@ internal static bool InsertIntoDescending(bool enforceUnique, uint currSiz return true; } + static void ShiftRight(bool isFull, int fromPosition, int size, Span values, Span weights) + { + var len = size - fromPosition; + var cs = isFull ? len - 1 : len; + var vf = values.Slice(fromPosition, cs); + vf.CopyTo(values.Slice(fromPosition+1, vf.Length)); + var wf = weights.Slice(fromPosition, cs); + wf.CopyTo(weights.Slice(fromPosition+1, wf.Length)); + } + internal static void RemoveAt(int index, Span values, Span weights) { values[(index + 1)..].CopyTo(values[index..]); diff --git a/BrightData/Types/Helper/WeightedGraphHelper.cs b/BrightData/Types/Helper/WeightedGraphHelper.cs new file mode 100644 index 00000000..519d50ce --- /dev/null +++ b/BrightData/Types/Helper/WeightedGraphHelper.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace BrightData.Types.Helper +{ + internal static class WeightedGraphHelper + { + public delegate ReadOnlySpan GetNeighboursDelegate(uint nodeIndex); + + public static RAT SearchFixedSize(uint q, uint entryPoint, ICalculateNodeWeights distanceCalculator, GetNeighboursDelegate getNeighbours) + where W : unmanaged, INumber, IMinMaxValue + where RAT : struct, IFixedSizeSortedArray + where CAT : struct, IFixedSizeSortedArray + { + var visited = new HashSet { entryPoint }; + var candidates = new CAT(); + var distanceEQ = distanceCalculator.GetWeight(q, entryPoint); + candidates.TryAdd(entryPoint, distanceEQ); + var ret = new RAT(); + ret.TryAdd(entryPoint, distanceEQ); + + while (candidates.Size > 0) { + var c = candidates.RemoveAt(0); + var f = ret.MaxValue; + if (distanceCalculator.GetWeight(c, q) > distanceCalculator.GetWeight(f, q)) + break; + + foreach (var neighbour in getNeighbours(c)) { + if(!visited.Add(neighbour)) + continue; + + f = ret.MaxValue; + if ((distanceEQ = distanceCalculator.GetWeight(neighbour, q)) < distanceCalculator.GetWeight(f, q)) { + candidates.TryAdd(neighbour, distanceEQ); + ret.TryAdd(neighbour, distanceEQ); + } + } + } + return ret; + } + } +} diff --git a/BrightData/Types/IndexedSortedArray.cs b/BrightData/Types/IndexedSortedArray.cs index e65ae2ba..36f1fd28 100644 --- a/BrightData/Types/IndexedSortedArray.cs +++ b/BrightData/Types/IndexedSortedArray.cs @@ -19,13 +19,17 @@ class Comparer(uint index) : IComparable public Span Values => CollectionsMarshal.AsSpan(_values); public uint Size => (uint)Values.Length; - public bool Add(T value) + public T this[int position] => _values[position]; + public T this[uint position] => _values[(int)position]; + + public void Add(T value) { _values.Add(value); - return SortedArrayHelper.InsertIndexed(Size - 1, value, Values); + if(_values.Count > 1) + SortedArrayHelper.InsertIndexed(Size - 1, value, Values); } - public ref T Get(uint itemIndex) + public ref T Find(uint itemIndex) { var arrayIndex = Values.BinarySearch(new Comparer(itemIndex)); if (arrayIndex >= 0) @@ -33,7 +37,7 @@ public ref T Get(uint itemIndex) return ref Unsafe.NullRef(); } - public bool TryGet(uint itemIndex, [NotNullWhen(true)]out T? value) + public bool TryFind(uint itemIndex, [NotNullWhen(true)]out T? value) { var arrayIndex = Values.BinarySearch(new Comparer(itemIndex)); if (arrayIndex >= 0) { diff --git a/BrightData/Types/SortedArray.cs b/BrightData/Types/SortedArray.cs index 3f6bf71f..88d71a06 100644 --- a/BrightData/Types/SortedArray.cs +++ b/BrightData/Types/SortedArray.cs @@ -18,16 +18,20 @@ public class SortedArray(int? capacity = null) : public Span Values => CollectionsMarshal.AsSpan(_values); public Span Weights => CollectionsMarshal.AsSpan(_weights); - public uint Size => (uint)Values.Length; + public uint Size => (uint)_values.Count; - public bool Add(W weight, in V item) + public (V Value, W Weight) this[int position] => (_values[position], _weights[position]); + public (V Value, W Weight) this[uint position] => (_values[(int)position], _weights[(int)position]); + + public void Add(in V item, W weight) { _values.Add(item); _weights.Add(weight); - return SortedArrayHelper.InsertIntoAscending(false, Size-1, uint.MaxValue, item, weight, Values, Weights); + if(_values.Count > 1) + SortedArrayHelper.InsertIntoAscending(false, Size-1, uint.MaxValue, item, weight, Values, Weights); } - public ref V Get(W weight) + public ref V Find(W weight) { var index = Weights.BinarySearch(weight); if (index >= 0) @@ -35,7 +39,7 @@ public ref V Get(W weight) return ref Unsafe.NullRef(); } - public bool TryGet(W weight, [NotNullWhen(true)]out V? value) + public bool TryFind(W weight, [NotNullWhen(true)]out V? value) { var index = Weights.BinarySearch(weight); if (index >= 0) {