Skip to content

Commit

Permalink
added indexed sorted array
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack Dermody committed Aug 2, 2024
1 parent 741fc75 commit 6f56351
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 60 deletions.
14 changes: 10 additions & 4 deletions BrightData/BrightData.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17767,16 +17767,22 @@
<typeparam name="W"></typeparam>
<typeparam name="AT"></typeparam>
</member>
<member name="M:BrightData.Types.Graph.FixedSizeWeightedGraph`3.Create(`0,System.Boolean)">
<member name="M:BrightData.Types.Graph.FixedSizeWeightedGraph`3.Add(`0)">
<inheritdoc />
</member>
<member name="M:BrightData.Types.Graph.FixedSizeWeightedGraph`3.Add(BrightData.IWeightedGraphNode{`0,`1})">
<member name="M:BrightData.Types.Graph.FixedSizeWeightedGraph`3.Add(`0,System.ReadOnlySpan{System.ValueTuple{System.UInt32,`1}})">
<inheritdoc />
</member>
<member name="P:BrightData.Types.Graph.FixedSizeWeightedGraph`3.Size">
<member name="M:BrightData.Types.Graph.FixedSizeWeightedGraph`3.Get(System.UInt32)">
<inheritdoc />
</member>
<member name="M:BrightData.Types.Graph.FixedSizeWeightedGraph`3.Get(System.UInt32)">
<member name="M:BrightData.Types.Graph.FixedSizeWeightedGraph`3.GetNeighbours(System.UInt32)">
<inheritdoc />
</member>
<member name="M:BrightData.Types.Graph.FixedSizeWeightedGraph`3.AddNeighbour(System.UInt32,System.UInt32,`1)">
<inheritdoc />
</member>
<member name="P:BrightData.Types.Graph.FixedSizeWeightedGraph`3.Size">
<inheritdoc />
</member>
<member name="M:BrightData.Types.Graph.FixedSizeWeightedGraph`3.Search``2(System.UInt32,System.UInt32,BrightData.ICalculateNodeWeights{`1})">
Expand Down
11 changes: 7 additions & 4 deletions BrightData/Interfaces.cs
Original file line number Diff line number Diff line change
Expand Up @@ -667,15 +667,18 @@ public interface IWeightedGraph<T, W> : IHaveSize
where T: IHaveSingleIndex
where W : unmanaged, INumber<W>, IMinMaxValue<W>
{
IWeightedGraphNode<T, W> Create(T value, bool addToGraph = true);
void Add(T value);
void Add(T value, ReadOnlySpan<(uint Index, W Weight)> neighbours);

void Add(IWeightedGraphNode<T, W> node);

IWeightedGraphNode<T, W> Get(uint index);
T Get(uint index);

RAT Search<RAT, CAT>(uint q, uint entryPoint, ICalculateNodeWeights<W> distanceCalculator)
where RAT : struct, IFixedSizeSortedArray<uint, W>
where CAT : struct, IFixedSizeSortedArray<uint, W>
;

ReadOnlySpan<uint> GetNeighbours(uint nodeIndex);

bool AddNeighbour(uint nodeIndex, uint neighbourIndex, W weight);
}
}
60 changes: 40 additions & 20 deletions BrightData/Types/Graph/FixedSizeWeightedGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -14,40 +15,59 @@ namespace BrightData.Types.Graph
/// <typeparam name="W"></typeparam>
/// <typeparam name="AT"></typeparam>
public class FixedSizeWeightedGraph<T, W, AT> : IWeightedGraph<T, W>
where T : IHaveSingleIndex
where T : unmanaged, IHaveSingleIndex
where W : unmanaged, INumber<W>, IMinMaxValue<W>
where AT : struct, IFixedSizeSortedArray<uint, W>
where AT : unmanaged, IFixedSizeSortedArray<uint, W>

{
readonly SortedArray<FixedSizeWeightedGraphNode<T, W, AT>, uint> _nodes = new();
readonly IndexedSortedArray<FixedSizeWeightedGraphNode<T, W, AT>> _nodes = new();

/// <inheritdoc />
public IWeightedGraphNode<T, W> Create(T value, bool addToGraph = true)
public void Add(T value)
{
var ret = new FixedSizeWeightedGraphNode<T, W, AT>(value);
if(addToGraph)
_nodes.Add(value.Index, ret);
return ret;
_nodes.Add(new FixedSizeWeightedGraphNode<T, W, AT>(value));
}

/// <inheritdoc />
public void Add(IWeightedGraphNode<T, W> node)
public void Add(T value, ReadOnlySpan<(uint Index, W Weight)> neighbours)
{
if(node is FixedSizeWeightedGraphNode<T, W, AT> same)
_nodes.Add(same.Index, same);
else {
var ret = new FixedSizeWeightedGraphNode<T, W, AT>(node.Value);
foreach (var (neighbour, weight) in node.WeightedNeighbours)
ret.AddNeighbour(neighbour, weight);
_nodes.Add(ret.Index, ret);
}
var node = new FixedSizeWeightedGraphNode<T, W, AT>(value);
foreach (var (index, weight) in neighbours)
node.AddNeighbour(index, weight);
_nodes.Add(node);
}

/// <inheritdoc />
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");
}

/// <inheritdoc />
public ReadOnlySpan<uint> GetNeighbours(uint nodeIndex)
{
ref var node = ref _nodes.Get(nodeIndex);
if (!Unsafe.IsNullRef(ref node))
return node.NeighbourSpan;
return ReadOnlySpan<uint>.Empty;
}

/// <inheritdoc />
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;
}

/// <inheritdoc />
public IWeightedGraphNode<T, W> Get(uint index) => _nodes.TryGet(index, out var ret) ? ret : throw new ArgumentException("Index not found");
public uint Size => _nodes.Size;

/// <inheritdoc />
public RAT Search<RAT, CAT>(uint q, uint entryPoint, ICalculateNodeWeights<W> distanceCalculator)
Expand All @@ -67,7 +87,7 @@ public RAT Search<RAT, CAT>(uint q, uint entryPoint, ICalculateNodeWeights<W> 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;

Expand Down
12 changes: 5 additions & 7 deletions BrightData/Types/Graph/FixedSizeWeightedGraphNode.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// A graph node with a (fixed size) maximum number of neighbours
/// </summary>
public record FixedSizeWeightedGraphNode<T, W, AT>(T Value) : IWeightedGraphNode<T, W>, IComparable<FixedSizeWeightedGraphNode<T, W, AT>>
where T : IHaveSingleIndex
public record struct FixedSizeWeightedGraphNode<T, W, AT>(T Value) : IWeightedGraphNode<T, W>, IComparable<FixedSizeWeightedGraphNode<T, W, AT>>, IHaveSingleIndex
where T : unmanaged, IHaveSingleIndex
where W : unmanaged, INumber<W>, IMinMaxValue<W>
where AT : struct, IFixedSizeSortedArray<uint, W>
where AT : unmanaged, IFixedSizeSortedArray<uint, W>
{
AT _neighbours = new();

Expand Down Expand Up @@ -54,7 +51,8 @@ public IEnumerable<uint> Neighbours
}

/// <inheritdoc />
public int CompareTo(FixedSizeWeightedGraphNode<T, W, AT>? other) => Value.Index.CompareTo(other?.Value.Index);
//public int CompareTo(FixedSizeWeightedGraphNode<T, W, AT>? other) => Value.Index.CompareTo(other?.Value.Index);
public int CompareTo(FixedSizeWeightedGraphNode<T, W, AT> other) => Value.Index.CompareTo(other.Value.Index);

/// <inheritdoc />
public override string ToString() => $"{Value}: {_neighbours}";
Expand Down
33 changes: 17 additions & 16 deletions BrightData/Types/Graph/HierarchicalNavigationSmallWorldGraph.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
namespace BrightData.Types.Graph
{
public class HierarchicalNavigationSmallWorldGraph<T, W, AT, BLAT>(BrightDataContext context, int maxLayers)
where T : IHaveSingleIndex
where T : unmanaged, IHaveSingleIndex
where W : unmanaged, INumber<W>, IMinMaxValue<W>, IBinaryFloatingPointIeee754<W>
where AT : struct, IFixedSizeSortedArray<uint, W>
where BLAT : struct, IFixedSizeSortedArray<uint, W>
where AT : unmanaged, IFixedSizeSortedArray<uint, W>
where BLAT : unmanaged, IFixedSizeSortedArray<uint, W>
{
public record NodeIndex(T Value, uint LayerIndex) : IHaveSingleIndex
public record struct NodeIndex(T Value, uint LayerIndex) : IHaveSingleIndex
{
public uint Index => Value.Index;
}
Expand All @@ -23,10 +23,12 @@ public record NodeIndex(T Value, uint LayerIndex) : IHaveSingleIndex
.Select(i => (IWeightedGraph<NodeIndex, W>)(i == 0 ? new FixedSizeWeightedGraph<NodeIndex, W, BLAT>() : new FixedSizeWeightedGraph<NodeIndex, W, AT>()))
.ToArray()
;
IWeightedGraphNode<NodeIndex, W>? _entryPoint = null;
NodeIndex? _entryPoint = null;

public void Add(IEnumerable<T> values, ICalculateNodeWeights<W> distanceCalculator)
{
Span<(uint, W)> newNodeNeighbours = stackalloc (uint, W)[32];

foreach (var value in values) {
var entryPoint = _entryPoint;
var level = GetRandomLevel();
Expand All @@ -37,26 +39,26 @@ public void Add(IEnumerable<T> values, ICalculateNodeWeights<W> distanceCalculat
entryPointLevel = entryPoint.Value.LayerIndex;
for (var i = entryPointLevel.Value; i > level; i--) {
var layer = _layers[i];
var w = layer.Search<FixedSizeSortedAscending1Array<uint, W>, AT>(value.Index, entryPoint.Index, distanceCalculator);
var w = layer.Search<FixedSizeSortedAscending1Array<uint, W>, 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<AT, BLAT>(value.Index, entryPoint.Index, distanceCalculator);
var w = layer.Search<AT, BLAT>(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)
Expand All @@ -67,7 +69,7 @@ public void Add(IEnumerable<T> values, ICalculateNodeWeights<W> distanceCalculat
public AT KnnSearch(uint q, ICalculateNodeWeights<W> 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<AT, BLAT>(q, entryPoint.Index, distanceCalculator);
entryPoint = layer.Get(w.MinValue);
Expand All @@ -83,8 +85,7 @@ public IEnumerable<uint> 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;
Expand Down
39 changes: 39 additions & 0 deletions BrightData/Types/Helper/SortedArrayHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,45 @@ namespace BrightData.Types.Helper
/// </summary>
public class SortedArrayHelper
{
internal static bool InsertIndexed<V>(uint currSize, V value, Span<V> 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<V, W>(bool enforceUnique, uint currSize, uint maxSize, V value, W weight, Span<V> values, Span<W> weights)
where V : IComparable<V>
where W : unmanaged, INumber<W>, IMinMaxValue<W>
Expand Down
47 changes: 47 additions & 0 deletions BrightData/Types/IndexedSortedArray.cs
Original file line number Diff line number Diff line change
@@ -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<T>(int? capacity = null) : IHaveSize
where T: unmanaged, IHaveSingleIndex
{
class Comparer(uint index) : IComparable<T>
{
public int CompareTo(T other) => index.CompareTo(other.Index);
}
readonly List<T> _values = capacity.HasValue ? new(capacity.Value) : new();

public Span<T> 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<T>();
}

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;
}
}
}
19 changes: 10 additions & 9 deletions BrightData/Types/SortedArray.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<V, W>(int? capacity = null, bool isAscending = true) : IHaveSize
where V : IComparable<V>
public class SortedArray<V, W>(int? capacity = null) :
IHaveSize
where V : unmanaged, IComparable<V>
where W : unmanaged, INumber<W>, IMinMaxValue<W>
{
readonly List<V> _values = capacity.HasValue ? new(capacity.Value) : new();
Expand All @@ -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<V>();
}

public bool TryGet(W weight, [NotNullWhen(true)]out V? value)
Expand Down

0 comments on commit 6f56351

Please sign in to comment.