Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Geometry_Engine: Update PointClustersDBSCAN to use DomainTree #1888

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions Data_Engine/Compute/DomainTreeClusters.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2020, the respective contributors. All rights reserved.
*
* Each contributor holds copyright over their respective contributions.
* The project versioning (Git) records all such contribution source information.
*
*
* The BHoM is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* The BHoM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using BH.oM.Data.Collections;
using BH.oM.Reflection.Attributes;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace BH.Engine.Data
{
public static partial class Compute
{
/***************************************************/
/**** Public Methods ****/
/***************************************************/

[Description("Clusters data in different collections using a DomainTree.")]
[Input("data", "Data to cluster.")]
[Input("toDomainBox", "Method which takes a item in the data and produces a DomainBox for the tree.")]
[Input("treeEvaluator", "Method which evaluates if the items within the second DomainBox could be in the same cluster as the first DomainBox.")]
[Input("itemEvaluator", "Method which evaluates if the two items should be in the same cluster.")]
[Input("minItemCount", "Lowest number of item in a cluster to return.")]
[Output("clusters", "Clusters where itemEvaluator is never true between items from different clusters.")]
public static List<List<T>> DomainTreeClusters<T>(this List<T> data, Func<T, DomainBox> toDomainBox, Func<DomainBox, DomainBox, bool> treeEvaluator, Func<T, T, bool> itemEvaluator, int minItemCount = 1)
{
if (data.Count == 0)
return new List<List<T>>();

List<T> items = new List<T>(data);
List<bool> check = items.Select(x => false).ToList();
List<DomainBox> domainBoxes = data.Select(x => toDomainBox(x)).ToList();
DomainTree<int> indexTree = Data.Create.DomainTree(domainBoxes.Select((x, i) => Data.Create.DomainTreeLeaf(i, x)));

List<List<T>> result = new List<List<T>>();
List<int> toEvaluate = new List<int>();

int count = -1;
for (int i = 0; i < items.Count; i++)
{
if (check[i])
continue;

result.Add(new List<T>());
count++;
toEvaluate.Add(i);
check[i] = true;

while (toEvaluate.Count > 0)
{
// Find all the neighbours for each item in toEvaluate, and add them in toEvaluate
foreach (int index in Data.Query.ItemsInRange<DomainTree<int>, int>(indexTree, x => treeEvaluator(x.DomainBox, domainBoxes[toEvaluate[0]])))
{
if (!check[index])
{
if (itemEvaluator(items[toEvaluate[0]], items[index]))
{
toEvaluate.Add(index);
check[index] = true;
}
}
}

// move the checked items from toEvaluate to result
result[count].Add(items[toEvaluate[0]]);
toEvaluate.RemoveAt(0);
}
}

return result.Where(list => list.Count >= minItemCount).ToList();
}

/***************************************************/
}
}

4 changes: 2 additions & 2 deletions Data_Engine/Create/DomainTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ public static DomainTree<T> DomainTree<T>(IEnumerable<DomainTree<T>> dataItems,
{
DomainTree<T> tree = new DomainTree<T>()
{
Children = children.ToList(),
DomainBox = children.Select(x => x.DomainBox).Aggregate((x, y) => x + y)
Children = children?.ToList() ?? new List<DomainTree<T>>(),
DomainBox = children?.Select(x => x?.DomainBox).Aggregate((x, y) => x + y)
};

return tree;
Expand Down
2 changes: 1 addition & 1 deletion Data_Engine/Create/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public static TNode Node<TNode, T>(IEnumerable<TNode> dataItems,

leafSize = Math.Max(leafSize, 2);

if (dataItems.Count() > leafSize)
if (dataItems != null && dataItems.Skip(leafSize).Any())
{
// Partition the data where each collection will form a child Node
IEnumerable<IEnumerable<TNode>> subLists = partitionMethod(dataItems);
Expand Down
1 change: 1 addition & 0 deletions Data_Engine/Data_Engine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Compute\DomainTreeClusters.cs" />
<Compile Include="Compute\ClusterDBSCAN.cs" />
<Compile Include="Compute\Series.cs" />
<Compile Include="Compute\FilterData.cs" />
Expand Down
6 changes: 3 additions & 3 deletions Data_Engine/Query/Children.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static partial class Query
[Output("nodes", "Child nodes of the input node.")]
public static IEnumerable<TNode> IChildren<TNode, T>(this TNode node) where TNode : INode<T>
{
return Children(node as dynamic);
return Children(node as dynamic) ?? new List<TNode>();
}

/***************************************************/
Expand All @@ -51,15 +51,15 @@ public static IEnumerable<TNode> IChildren<TNode, T>(this TNode node) where TNod
[Output("nodes", "Child nodes of the input node.")]
public static List<DomainTree<T>> Children<T>(this DomainTree<T> node)
{
return node.Children;
return node?.Children ?? new List<DomainTree<T>>();
}


/***************************************************/
/**** Private Methods ****/
/***************************************************/

public static IEnumerable<INode<T>> Children<T>(this INode<T> node)
private static IEnumerable<INode<T>> Children<T>(this INode<T> node)
{
Reflection.Compute.RecordError("The method Values is not implemented for " + node.GetType().Name);
return null;
Expand Down
12 changes: 9 additions & 3 deletions Data_Engine/Query/ClosestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,16 @@ public static IEnumerable<T> ClosestData<T>(this DomainTree<T> tree,
double maxDistance = double.PositiveInfinity,
double tolerance = Tolerance.Distance)
{
Func<DomainTree<T>, double> evaluationMethod = (x) => x.DomainBox.SquareDistance(searchBox);
if (searchBox == null)
return new List<T>();

Func<DomainTree<T>, double> evaluationMethod = (x) => x.DomainBox?.SquareDistance(searchBox) ?? double.PositiveInfinity;

Func<DomainTree<T>, double> worstCaseMethod;
if (tightBox)
worstCaseMethod = (x) => x.DomainBox.FurthestTightSquareDistance(searchBox);
worstCaseMethod = (x) => x.DomainBox?.FurthestTightSquareDistance(searchBox) ?? double.PositiveInfinity;
else
worstCaseMethod = (x) => x.DomainBox.FurthestSquareDistance(searchBox);
worstCaseMethod = (x) => x.DomainBox?.FurthestSquareDistance(searchBox) ?? double.PositiveInfinity;

return ClosestData<DomainTree<T>, T>(tree, evaluationMethod, worstCaseMethod, maxDistance * maxDistance, tolerance * tolerance);
}
Expand All @@ -92,6 +95,9 @@ public static IEnumerable<T> ClosestData<TNode, T>(this TNode tree,
double maxEvaluation = double.PositiveInfinity,
double tolerance = Tolerance.Distance) where TNode : INode<T>
{
if (tree == null)
return new List<T>();

List<Tuple<double, TNode>> list = new List<Tuple<double, TNode>>()
{
new Tuple<double, TNode>(evaluationMethod(tree), tree)
Expand Down
7 changes: 5 additions & 2 deletions Data_Engine/Query/ItemsInRange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ public static partial class Query
[Output("data", "All data in the tree which DomainBox is in range of the box.")]
public static IEnumerable<T> ItemsInRange<T>(this DomainTree<T> tree, DomainBox box, double tolerance = Tolerance.Distance)
{
Func<DomainTree<T>, bool> isWithinSearch = x => x.DomainBox.IsInRange(box, tolerance);
if (box == null)
return new List<T>();

Func<DomainTree<T>, bool> isWithinSearch = x => x.DomainBox?.IsInRange(box, tolerance) ?? false;

return ItemsInRange<DomainTree<T>, T>(tree, isWithinSearch);
}
Expand All @@ -60,7 +63,7 @@ public static IEnumerable<T> ItemsInRange<T>(this DomainTree<T> tree, DomainBox
[Output("data", "All data in the tree which nodes returned true for the isWithinSearch method.")]
public static IEnumerable<T> ItemsInRange<TNode, T>(this TNode tree, Func<TNode, bool> isWithinSearch) where TNode : INode<T>
{
if (isWithinSearch(tree))
if (tree != null && isWithinSearch(tree))
{
return tree.IChildren<TNode, T>().SelectMany(x => ItemsInRange<TNode, T>(x, isWithinSearch)).Concat(tree.IValues());
}
Expand Down
6 changes: 3 additions & 3 deletions Data_Engine/Query/Values.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public static List<CustomObject> Values(this Table table)
[Output("data", "Data values contained in this node.")]
public static IEnumerable<T> IValues<T>(this INode<T> node)
{
return Values(node as dynamic);
return Values(node as dynamic) ?? new List<T>();
}

/***************************************************/
Expand All @@ -62,15 +62,15 @@ public static IEnumerable<T> IValues<T>(this INode<T> node)
[Output("data", "Data values contained in this node.")]
public static List<T> Values<T>(this DomainTree<T> node)
{
return node.Values ?? new List<T>();
return node?.Values ?? new List<T>();
}


/***************************************************/
/**** Private Methods ****/
/***************************************************/

public static IEnumerable<T> Values<T>(this INode<T> node)
private static IEnumerable<T> Values<T>(this INode<T> node)
{
Reflection.Compute.RecordError("The method Values is not implemented for " + node.GetType().Name);
return null;
Expand Down
18 changes: 14 additions & 4 deletions Geometry_Engine/Compute/PointClustersDBSCAN.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

using BH.Engine.Data;
using BH.oM.Geometry;
using BH.oM.Data.Collections;
using System;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -33,12 +34,21 @@ public static partial class Compute
/***************************************************/
/**** public Methods - Vectors ****/
/***************************************************/

public static List<List<Point>> PointClustersDBSCAN(this List<Point> points, double maxDist, int minPointCount = 1)
{
double maxSqrDist = maxDist * maxDist;
Func<Point, Point, bool> metricFunction = (x, y) => x.SquareDistance(y) <= maxSqrDist;
return points.ClusterDBSCAN(metricFunction, minPointCount);
double sqDist = maxDist * maxDist;
Func<Point, DomainBox> toDomainBox = a => new DomainBox()
{
Domains = new Domain[] {
new Domain(a.X, a.X),
new Domain(a.Y, a.Y),
new Domain(a.Z, a.Z),
}
};
Func<DomainBox, DomainBox, bool> treeFunction = (a, b) => a.SquareDistance(b) < sqDist;
Func<Point, Point, bool> itemFunction = (a, b) => true; // The distance between the boxes is enough to determine if a Point is in range
return Data.Compute.DomainTreeClusters<Point>(points, toDomainBox, treeFunction, itemFunction, minPointCount);
}

/***************************************************/
Expand Down