Skip to content

Commit e6f76e1

Browse files
committed
feat: helper utilities to turn associations into maps
1 parent 018d09d commit e6f76e1

File tree

2 files changed

+219
-0
lines changed

2 files changed

+219
-0
lines changed

utils/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ target_include_directories(edm4eic_utils
2020

2121
install(FILES
2222
include/edm4eic/analysis_utils.h
23+
include/edm4eic/association_utils.h
2324
include/edm4eic/unit_system.h
2425
include/edm4eic/vector_utils.h
2526
include/edm4eic/vector_utils_legacy.h
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
// Copyright (C) 2025 Wouter Deconinck
3+
4+
#ifndef EDM4EIC_UTILS_ASSOCIATION_HH
5+
#define EDM4EIC_UTILS_ASSOCIATION_HH
6+
7+
#include <podio/ObjectID.h>
8+
#include <unordered_map>
9+
#include <vector>
10+
#include <ranges>
11+
#include <type_traits>
12+
13+
namespace edm4eic {
14+
15+
/**
16+
* @brief Helper class to efficiently lookup associations between objects.
17+
*
18+
* This class provides O(1) lookup time for associations by building hash maps
19+
* from association collections. It handles both one-to-one and one-to-many
20+
* relationships, and satisfies C++20 range concepts for compatibility with
21+
* standard algorithms.
22+
*
23+
* Satisfies: std::ranges::forward_range, std::ranges::sized_range
24+
*
25+
* When 'from' and 'to' types are different, provides operator[] for natural lookup syntax.
26+
* Named methods are always available regardless of type differences.
27+
*
28+
* @tparam AssociationCollection Type of the association collection (e.g., MCRecoCalorimeterHitAssociationCollection)
29+
* @tparam GetFromObjectFunc Function type to extract the 'from' object from an association
30+
* @tparam GetToObjectFunc Function type to extract the 'to' object from an association
31+
*
32+
* Example usage:
33+
* @code
34+
* // For rec->sim associations
35+
* auto lookup = make_association_lookup(
36+
* mchitassociations,
37+
* [](const auto& assoc) { return assoc.getRawHit(); }, // from: rec
38+
* [](const auto& assoc) { return assoc.getSimHit(); } // to: sim
39+
* );
40+
*
41+
* // Natural operator[] syntax when types are different
42+
* auto sim_hits = lookup[raw_hit]; // O(1) lookup
43+
* for (const auto& sim_hit : sim_hits) {
44+
* // Process sim_hit
45+
* }
46+
*
47+
* // Named methods always available
48+
* auto sim_hits_alt = lookup.lookup_from_to(raw_hit);
49+
* auto raw_hits = lookup.lookup_to_from(sim_hit);
50+
*
51+
* // Can also iterate over all from->to mappings
52+
* for (const auto& [from_id, to_objs] : lookup.from_to_view()) {
53+
* // Process mapping
54+
* }
55+
*
56+
* // Works with C++20 ranges
57+
* auto filtered = lookup.from_to_view()
58+
* | std::views::filter([](const auto& p) { return p.second.size() > 1; });
59+
* @endcode
60+
*/
61+
template <typename AssociationCollection, typename GetFromObjectFunc, typename GetToObjectFunc>
62+
class association_lookup {
63+
public:
64+
using from_object_t = std::decay_t<decltype(std::declval<GetFromObjectFunc>()(
65+
*std::declval<const AssociationCollection*>()->begin()))>;
66+
using to_object_t = std::decay_t<decltype(std::declval<GetToObjectFunc>()(
67+
*std::declval<const AssociationCollection*>()->begin()))>;
68+
69+
using from_to_map_t = std::unordered_map<podio::ObjectID, std::vector<to_object_t>>;
70+
using to_from_map_t = std::unordered_map<podio::ObjectID, std::vector<from_object_t>>;
71+
72+
// Concept to check if from and to types are different
73+
static constexpr bool different_types = !std::is_same_v<from_object_t, to_object_t>;
74+
75+
/**
76+
* @brief Construct an association lookup helper
77+
*
78+
* @param associations The association collection to index
79+
* @param get_from_object Function to extract 'from' object from association
80+
* @param get_to_object Function to extract 'to' object from association
81+
*/
82+
association_lookup(const AssociationCollection* associations, GetFromObjectFunc get_from_object,
83+
GetToObjectFunc get_to_object)
84+
: m_get_from_object(get_from_object), m_get_to_object(get_to_object) {
85+
if (associations) {
86+
build_maps(associations);
87+
}
88+
}
89+
90+
/**
91+
* @brief Lookup 'to' objects using operator[] (only when types differ)
92+
*
93+
* @param from_obj The 'from' object
94+
* @return Vector of associated 'to' objects (empty if none found)
95+
*/
96+
std::vector<to_object_t> operator[](const from_object_t& from_obj) const
97+
requires different_types
98+
{
99+
return lookup_from_to(from_obj);
100+
}
101+
102+
/**
103+
* @brief Lookup 'to' objects associated with a 'from' object
104+
*
105+
* @param from_obj The 'from' object
106+
* @return Vector of associated 'to' objects (empty if none found)
107+
*/
108+
std::vector<to_object_t> lookup_from_to(const from_object_t& from_obj) const {
109+
auto it = m_from_to_map.find(from_obj.getObjectID());
110+
if (it != m_from_to_map.end()) {
111+
return it->second;
112+
}
113+
return {};
114+
}
115+
116+
/**
117+
* @brief Lookup 'from' objects associated with a 'to' object
118+
*
119+
* @param to_obj The 'to' object
120+
* @return Vector of associated 'from' objects (empty if none found)
121+
*/
122+
std::vector<from_object_t> lookup_to_from(const to_object_t& to_obj) const {
123+
auto it = m_to_from_map.find(to_obj.getObjectID());
124+
if (it != m_to_from_map.end()) {
125+
return it->second;
126+
}
127+
return {};
128+
}
129+
130+
/**
131+
* @brief Check if a 'from' object has any associations
132+
*
133+
* @param from_obj The 'from' object
134+
* @return true if associations exist
135+
*/
136+
bool has_from_associations(const from_object_t& from_obj) const {
137+
return m_from_to_map.find(from_obj.getObjectID()) != m_from_to_map.end();
138+
}
139+
140+
/**
141+
* @brief Check if a 'to' object has any associations
142+
*
143+
* @param to_obj The 'to' object
144+
* @return true if associations exist
145+
*/
146+
bool has_to_associations(const to_object_t& to_obj) const {
147+
return m_to_from_map.find(to_obj.getObjectID()) != m_to_from_map.end();
148+
}
149+
150+
/**
151+
* @brief Get view of from->to map for iteration (satisfies std::ranges::forward_range)
152+
*/
153+
const from_to_map_t& from_to_view() const { return m_from_to_map; }
154+
155+
/**
156+
* @brief Get view of to->from map for iteration (satisfies std::ranges::forward_range)
157+
*/
158+
const to_from_map_t& to_from_view() const { return m_to_from_map; }
159+
160+
/**
161+
* @brief Get the total number of associations indexed
162+
*/
163+
size_t size() const { return m_total_associations; }
164+
165+
/**
166+
* @brief Check if the lookup is empty
167+
*/
168+
bool empty() const { return m_total_associations == 0; }
169+
170+
// Iterator support for range-based for loops over from->to map
171+
auto begin() const { return m_from_to_map.begin(); }
172+
auto end() const { return m_from_to_map.end(); }
173+
auto cbegin() const { return m_from_to_map.cbegin(); }
174+
auto cend() const { return m_from_to_map.cend(); }
175+
176+
private:
177+
void build_maps(const AssociationCollection* associations) {
178+
for (const auto& assoc : *associations) {
179+
auto from_obj = m_get_from_object(assoc);
180+
auto to_obj = m_get_to_object(assoc);
181+
182+
m_from_to_map[from_obj.getObjectID()].push_back(to_obj);
183+
m_to_from_map[to_obj.getObjectID()].push_back(from_obj);
184+
++m_total_associations;
185+
}
186+
}
187+
188+
GetFromObjectFunc m_get_from_object;
189+
GetToObjectFunc m_get_to_object;
190+
191+
from_to_map_t m_from_to_map;
192+
to_from_map_t m_to_from_map;
193+
size_t m_total_associations = 0;
194+
};
195+
196+
/**
197+
* @brief Helper function to create an association_lookup with type deduction
198+
*
199+
* Example:
200+
* @code
201+
* auto lookup = make_association_lookup(
202+
* mchitassociations,
203+
* [](const auto& a) { return a.getRawHit(); }, // from
204+
* [](const auto& a) { return a.getSimHit(); } // to
205+
* );
206+
* @endcode
207+
*/
208+
template <typename AssociationCollection, typename GetFromObjectFunc, typename GetToObjectFunc>
209+
association_lookup<AssociationCollection, GetFromObjectFunc, GetToObjectFunc>
210+
make_association_lookup(const AssociationCollection* associations, GetFromObjectFunc get_from_object,
211+
GetToObjectFunc get_to_object) {
212+
return association_lookup<AssociationCollection, GetFromObjectFunc, GetToObjectFunc>(
213+
associations, get_from_object, get_to_object);
214+
}
215+
216+
} // namespace edm4eic
217+
218+
#endif // EDM4EIC_UTILS_ASSOCIATION_HH

0 commit comments

Comments
 (0)