diff --git a/BrightData/BrightData.xml b/BrightData/BrightData.xml
index 18c57f58..f6d066ee 100644
--- a/BrightData/BrightData.xml
+++ b/BrightData/BrightData.xml
@@ -17767,16 +17767,22 @@
-
+
-
+
-
+
-
+
+
+
+
+
+
+
diff --git a/BrightData/Interfaces.cs b/BrightData/Interfaces.cs
index d42f627e..5cd82836 100644
--- a/BrightData/Interfaces.cs
+++ b/BrightData/Interfaces.cs
@@ -667,15 +667,18 @@ public interface IWeightedGraph : IHaveSize
where T: IHaveSingleIndex
where W : unmanaged, INumber, IMinMaxValue
{
- IWeightedGraphNode Create(T value, bool addToGraph = true);
+ void Add(T value);
+ void Add(T value, ReadOnlySpan<(uint Index, W Weight)> neighbours);
- void Add(IWeightedGraphNode node);
-
- IWeightedGraphNode Get(uint index);
+ T Get(uint index);
RAT Search(uint q, uint entryPoint, ICalculateNodeWeights distanceCalculator)
where RAT : struct, IFixedSizeSortedArray
where CAT : struct, IFixedSizeSortedArray
;
+
+ ReadOnlySpan GetNeighbours(uint nodeIndex);
+
+ bool AddNeighbour(uint nodeIndex, uint neighbourIndex, W weight);
}
}
diff --git a/BrightData/Types/Graph/FixedSizeWeightedGraph.cs b/BrightData/Types/Graph/FixedSizeWeightedGraph.cs
index ef2614fa..619e0221 100644
--- a/BrightData/Types/Graph/FixedSizeWeightedGraph.cs
+++ b/BrightData/Types/Graph/FixedSizeWeightedGraph.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
+using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
@@ -14,40 +15,59 @@ namespace BrightData.Types.Graph
///
///
public class FixedSizeWeightedGraph : IWeightedGraph
- where T : IHaveSingleIndex
+ where T : unmanaged, IHaveSingleIndex
where W : unmanaged, INumber, IMinMaxValue
- where AT : struct, IFixedSizeSortedArray
+ where AT : unmanaged, IFixedSizeSortedArray
{
- readonly SortedArray, uint> _nodes = new();
+ readonly IndexedSortedArray> _nodes = new();
///
- public IWeightedGraphNode Create(T value, bool addToGraph = true)
+ public void Add(T value)
{
- var ret = new FixedSizeWeightedGraphNode(value);
- if(addToGraph)
- _nodes.Add(value.Index, ret);
- return ret;
+ _nodes.Add(new FixedSizeWeightedGraphNode(value));
}
///
- public void Add(IWeightedGraphNode node)
+ public void Add(T value, ReadOnlySpan<(uint Index, W Weight)> neighbours)
{
- if(node is FixedSizeWeightedGraphNode same)
- _nodes.Add(same.Index, same);
- else {
- var ret = new FixedSizeWeightedGraphNode(node.Value);
- foreach (var (neighbour, weight) in node.WeightedNeighbours)
- ret.AddNeighbour(neighbour, weight);
- _nodes.Add(ret.Index, ret);
- }
+ var node = new FixedSizeWeightedGraphNode(value);
+ foreach (var (index, weight) in neighbours)
+ node.AddNeighbour(index, weight);
+ _nodes.Add(node);
}
///
- public uint Size => _nodes.Size;
+ public T Get(uint nodeIndex)
+ {
+ 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");
+ }
+
+ ///
+ public ReadOnlySpan GetNeighbours(uint nodeIndex)
+ {
+ ref var node = ref _nodes.Get(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.Get(nodeIndex);
+ if (!Unsafe.IsNullRef(ref node)) {
+ return node.AddNeighbour(neighbourIndex, weight);
+ }
+
+ return false;
+ }
///
- public IWeightedGraphNode Get(uint index) => _nodes.TryGet(index, out var ret) ? ret : throw new ArgumentException("Index not found");
+ public uint Size => _nodes.Size;
///
public RAT Search(uint q, uint entryPoint, ICalculateNodeWeights distanceCalculator)
@@ -67,7 +87,7 @@ public RAT Search(uint q, uint entryPoint, ICalculateNodeWeights di
if (distanceCalculator.GetWeight(c, q) > distanceCalculator.GetWeight(f, q))
break;
- foreach (var neighbour in Get(c).NeighbourSpan) {
+ foreach (var neighbour in GetNeighbours(c)) {
if(!visited.Add(neighbour))
continue;
diff --git a/BrightData/Types/Graph/FixedSizeWeightedGraphNode.cs b/BrightData/Types/Graph/FixedSizeWeightedGraphNode.cs
index 85cbd546..c03b561e 100644
--- a/BrightData/Types/Graph/FixedSizeWeightedGraphNode.cs
+++ b/BrightData/Types/Graph/FixedSizeWeightedGraphNode.cs
@@ -1,19 +1,16 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Numerics;
-using System.Text;
-using System.Threading.Tasks;
namespace BrightData.Types.Graph
{
///
/// A graph node with a (fixed size) maximum number of neighbours
///
- public record FixedSizeWeightedGraphNode(T Value) : IWeightedGraphNode, IComparable>
- where T : IHaveSingleIndex
+ public record struct FixedSizeWeightedGraphNode(T Value) : IWeightedGraphNode, IComparable>, IHaveSingleIndex
+ where T : unmanaged, IHaveSingleIndex
where W : unmanaged, INumber, IMinMaxValue
- where AT : struct, IFixedSizeSortedArray
+ where AT : unmanaged, IFixedSizeSortedArray
{
AT _neighbours = new();
@@ -54,7 +51,8 @@ public IEnumerable Neighbours
}
///
- public int CompareTo(FixedSizeWeightedGraphNode? other) => Value.Index.CompareTo(other?.Value.Index);
+ //public int CompareTo(FixedSizeWeightedGraphNode? other) => Value.Index.CompareTo(other?.Value.Index);
+ public int CompareTo(FixedSizeWeightedGraphNode other) => Value.Index.CompareTo(other.Value.Index);
///
public override string ToString() => $"{Value}: {_neighbours}";
diff --git a/BrightData/Types/Graph/HierarchicalNavigationSmallWorldGraph.cs b/BrightData/Types/Graph/HierarchicalNavigationSmallWorldGraph.cs
index 4882fb85..425763d6 100644
--- a/BrightData/Types/Graph/HierarchicalNavigationSmallWorldGraph.cs
+++ b/BrightData/Types/Graph/HierarchicalNavigationSmallWorldGraph.cs
@@ -9,12 +9,12 @@
namespace BrightData.Types.Graph
{
public class HierarchicalNavigationSmallWorldGraph(BrightDataContext context, int maxLayers)
- where T : IHaveSingleIndex
+ where T : unmanaged, IHaveSingleIndex
where W : unmanaged, INumber, IMinMaxValue, IBinaryFloatingPointIeee754
- where AT : struct, IFixedSizeSortedArray
- where BLAT : struct, IFixedSizeSortedArray
+ where AT : unmanaged, IFixedSizeSortedArray
+ where BLAT : unmanaged, IFixedSizeSortedArray
{
- public record NodeIndex(T Value, uint LayerIndex) : IHaveSingleIndex
+ public record struct NodeIndex(T Value, uint LayerIndex) : IHaveSingleIndex
{
public uint Index => Value.Index;
}
@@ -23,10 +23,12 @@ public record NodeIndex(T Value, uint LayerIndex) : IHaveSingleIndex
.Select(i => (IWeightedGraph)(i == 0 ? new FixedSizeWeightedGraph() : new FixedSizeWeightedGraph()))
.ToArray()
;
- IWeightedGraphNode? _entryPoint = null;
+ NodeIndex? _entryPoint = null;
public void Add(IEnumerable values, ICalculateNodeWeights distanceCalculator)
{
+ Span<(uint, W)> newNodeNeighbours = stackalloc (uint, W)[32];
+
foreach (var value in values) {
var entryPoint = _entryPoint;
var level = GetRandomLevel();
@@ -37,26 +39,26 @@ public void Add(IEnumerable values, ICalculateNodeWeights distanceCalculat
entryPointLevel = entryPoint.Value.LayerIndex;
for (var i = entryPointLevel.Value; i > level; i--) {
var layer = _layers[i];
- var w = layer.Search, AT>(value.Index, entryPoint.Index, distanceCalculator);
+ var w = layer.Search, AT>(value.Index, entryPoint.Value.Index, distanceCalculator);
entryPoint = layer.Get(w.MinValue);
}
}
// add to levels
- var from = Math.Min(level, entryPoint?.Value.LayerIndex ?? int.MaxValue);
+ var from = Math.Min(level, entryPoint?.LayerIndex ?? int.MaxValue);
for(var i = level; i > from; i--)
- _layers[i].Create(new NodeIndex(value, (uint)i));
+ _layers[i].Add(new NodeIndex(value, (uint)i));
for (var i = from; i >= 0; i--) {
var layer = _layers[i];
- var newNode = layer.Create(new NodeIndex(value, (uint)i), false);
+ var newNodeIndex = 0;
if (entryPoint is not null) {
- var w = layer.Search(value.Index, entryPoint.Index, distanceCalculator);
+ var w = layer.Search(value.Index, entryPoint.Value.Index, distanceCalculator);
foreach (var (ni, nw) in w.Elements) {
- layer.Get(ni).AddNeighbour(value.Index, nw);
- newNode.AddNeighbour(ni, nw);
+ layer.AddNeighbour(ni, value.Index, nw);
+ newNodeNeighbours[newNodeIndex++] = (ni, nw);
}
}
- layer.Add(newNode);
+ layer.Add(new NodeIndex(value, (uint)i), newNodeNeighbours[..newNodeIndex]);
}
if(!entryPointLevel.HasValue || level > entryPointLevel.Value)
@@ -67,7 +69,7 @@ public void Add(IEnumerable values, ICalculateNodeWeights distanceCalculat
public AT KnnSearch(uint q, ICalculateNodeWeights distanceCalculator)
{
var entryPoint = _entryPoint ?? throw new Exception("No nodes in graph");
- for (var i = (int)entryPoint.Value.LayerIndex; i > 0; i--) {
+ 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);
@@ -83,8 +85,7 @@ public IEnumerable BreadthFirstSearch(uint index)
queue.Enqueue(index);
while (queue.Count > 0) {
- var node = layer.Get(queue.Dequeue());
- foreach (var neighbour in node.Neighbours) {
+ foreach (var neighbour in layer.GetNeighbours(queue.Dequeue()).ToArray()) {
if(!visited.Add(neighbour))
continue;
yield return neighbour;
diff --git a/BrightData/Types/Helper/SortedArrayHelper.cs b/BrightData/Types/Helper/SortedArrayHelper.cs
index c1dab234..10348215 100644
--- a/BrightData/Types/Helper/SortedArrayHelper.cs
+++ b/BrightData/Types/Helper/SortedArrayHelper.cs
@@ -8,6 +8,45 @@ namespace BrightData.Types.Helper
///
public class SortedArrayHelper
{
+ internal static bool InsertIndexed(uint currSize, V value, Span values)
+ where V : unmanaged, IHaveSingleIndex
+ {
+ var size = (int)currSize;
+ var index = value.Index;
+
+ // use binary search to find the insertion position
+ int left = 0,
+ right = size - 1,
+ insertPosition = size
+ ;
+ while (left <= right)
+ {
+ var mid = left + (right - left) / 2;
+ if (values[mid].Index > index)
+ {
+ insertPosition = mid;
+ right = mid - 1;
+ }
+ else
+ {
+ left = mid + 1;
+ }
+ }
+
+ if (insertPosition != size)
+ {
+ // shuffle to make room
+ 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)
where V : IComparable
where W : unmanaged, INumber, IMinMaxValue
diff --git a/BrightData/Types/IndexedSortedArray.cs b/BrightData/Types/IndexedSortedArray.cs
new file mode 100644
index 00000000..e65ae2ba
--- /dev/null
+++ b/BrightData/Types/IndexedSortedArray.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using BrightData.Types.Helper;
+
+namespace BrightData.Types
+{
+ public class IndexedSortedArray(int? capacity = null) : IHaveSize
+ where T: unmanaged, IHaveSingleIndex
+ {
+ class Comparer(uint index) : IComparable
+ {
+ public int CompareTo(T other) => index.CompareTo(other.Index);
+ }
+ readonly List _values = capacity.HasValue ? new(capacity.Value) : new();
+
+ public Span Values => CollectionsMarshal.AsSpan(_values);
+ public uint Size => (uint)Values.Length;
+
+ public bool Add(T value)
+ {
+ _values.Add(value);
+ return SortedArrayHelper.InsertIndexed(Size - 1, value, Values);
+ }
+
+ public ref T Get(uint itemIndex)
+ {
+ var arrayIndex = Values.BinarySearch(new Comparer(itemIndex));
+ if (arrayIndex >= 0)
+ return ref Values[arrayIndex];
+ return ref Unsafe.NullRef();
+ }
+
+ public bool TryGet(uint itemIndex, [NotNullWhen(true)]out T? value)
+ {
+ var arrayIndex = Values.BinarySearch(new Comparer(itemIndex));
+ if (arrayIndex >= 0) {
+ value = _values[arrayIndex];
+ return true;
+ }
+ value = default;
+ return false;
+ }
+ }
+}
diff --git a/BrightData/Types/SortedArray.cs b/BrightData/Types/SortedArray.cs
index 60b3ddd9..3f6bf71f 100644
--- a/BrightData/Types/SortedArray.cs
+++ b/BrightData/Types/SortedArray.cs
@@ -2,13 +2,15 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BrightData.Types.Helper;
namespace BrightData.Types
{
- public class SortedArray(int? capacity = null, bool isAscending = true) : IHaveSize
- where V : IComparable
+ public class SortedArray(int? capacity = null) :
+ IHaveSize
+ where V : unmanaged, IComparable
where W : unmanaged, INumber, IMinMaxValue
{
readonly List _values = capacity.HasValue ? new(capacity.Value) : new();
@@ -22,16 +24,15 @@ public bool Add(W weight, in V item)
{
_values.Add(item);
_weights.Add(weight);
- return isAscending
- ? SortedArrayHelper.InsertIntoAscending(false, Size-1, uint.MaxValue, item, weight, Values, Weights)
- : SortedArrayHelper.InsertIntoDescending(false, Size-1, uint.MaxValue, item, weight, Values, Weights)
- ;
+ return SortedArrayHelper.InsertIntoAscending(false, Size-1, uint.MaxValue, item, weight, Values, Weights);
}
- public void RemoveAt(uint index)
+ public ref V Get(W weight)
{
- _values.RemoveAt((int)index);
- _weights.RemoveAt((int)index);
+ var index = Weights.BinarySearch(weight);
+ if (index >= 0)
+ return ref Values[index];
+ return ref Unsafe.NullRef();
}
public bool TryGet(W weight, [NotNullWhen(true)]out V? value)