/* * Copyright 2016 The Cartographer Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ namespace CartographerSharp.Mapping.Internal; /// /// A class that tracks the connectivity structure between trajectories. /// /// Connectivity includes both the count ("How many times have I _directly_ /// connected trajectories i and j?") and the transitive connectivity. /// /// Uses Union-Find (disjoint set forest) algorithm for efficient transitive /// connectivity tracking. /// /// Match C++ ConnectedComponents (connected_components.cc) /// public class ConnectedComponents { private readonly object _lock = new(); // Tracks transitive connectivity using a disjoint set forest, i.e. each // entry points towards the representative for the given trajectory. private readonly Dictionary _forest = new(); // Tracks the number of direct connections between a pair of trajectories. private readonly Dictionary<(int, int), int> _connectionMap = new(); /// /// Add a trajectory which is initially connected to only itself. /// public void Add(int trajectoryId) { lock (_lock) { // Use TryAdd to avoid overwriting existing entries _forest.TryAdd(trajectoryId, trajectoryId); } } /// /// Connect two trajectories. If either trajectory is untracked, it will be /// tracked. This function is invariant to the order of its arguments. Repeated /// calls to Connect increment the connectivity count. /// public void Connect(int trajectoryIdA, int trajectoryIdB) { lock (_lock) { Union(trajectoryIdA, trajectoryIdB); var sortedPair = (Math.Min(trajectoryIdA, trajectoryIdB), Math.Max(trajectoryIdA, trajectoryIdB)); if (!_connectionMap.TryGetValue(sortedPair, out var count)) { count = 0; } _connectionMap[sortedPair] = count + 1; } } /// /// Determines if two trajectories have been (transitively) connected. If /// either trajectory is not being tracked, returns false, except when it is /// the same trajectory, where it returns true. This function is invariant to /// the order of its arguments. /// public bool TransitivelyConnected(int trajectoryIdA, int trajectoryIdB) { if (trajectoryIdA == trajectoryIdB) { return true; } lock (_lock) { if (!_forest.ContainsKey(trajectoryIdA) || !_forest.ContainsKey(trajectoryIdB)) { return false; } return FindSet(trajectoryIdA) == FindSet(trajectoryIdB); } } /// /// Return the number of _direct_ connections between 'trajectoryIdA' and /// 'trajectoryIdB'. If either trajectory is not being tracked, returns 0. /// This function is invariant to the order of its arguments. /// public int ConnectionCount(int trajectoryIdA, int trajectoryIdB) { lock (_lock) { var sortedPair = (Math.Min(trajectoryIdA, trajectoryIdB), Math.Max(trajectoryIdA, trajectoryIdB)); return _connectionMap.TryGetValue(sortedPair, out var count) ? count : 0; } } /// /// The trajectory IDs, grouped by connectivity. /// public List> Components() { lock (_lock) { // Map from cluster exemplar -> growing cluster var map = new Dictionary>(); foreach (var entry in _forest) { var representative = FindSet(entry.Key); if (!map.TryGetValue(representative, out var component)) { component = []; map[representative] = component; } component.Add(entry.Key); } return [.. map.Values]; } } /// /// The list of trajectory IDs that belong to the same connected component as /// 'trajectoryId'. /// public List GetComponent(int trajectoryId) { lock (_lock) { if (!_forest.ContainsKey(trajectoryId)) { return [trajectoryId]; } var setId = FindSet(trajectoryId); var trajectoryIds = new List(); foreach (var entry in _forest) { if (FindSet(entry.Key) == setId) { trajectoryIds.Add(entry.Key); } } return trajectoryIds; } } /// /// Find the representative and compresses the path to it. /// Must be called with lock held. /// private int FindSet(int trajectoryId) { if (!_forest.TryGetValue(trajectoryId, out var parent)) { return trajectoryId; } if (trajectoryId != parent) { // Path compression for efficiency _forest[trajectoryId] = FindSet(parent); } return _forest[trajectoryId]; } /// /// Union two sets. /// Must be called with lock held. /// private void Union(int trajectoryIdA, int trajectoryIdB) { // Add trajectories if not already tracked _forest.TryAdd(trajectoryIdA, trajectoryIdA); _forest.TryAdd(trajectoryIdB, trajectoryIdB); var representativeA = FindSet(trajectoryIdA); var representativeB = FindSet(trajectoryIdB); _forest[representativeA] = representativeB; } }