Version: SMASH-3.4
clebschgordan_lookup.h
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2023,2026
3  * SMASH Team
4  *
5  * GNU General Public License (GPLv3 or later)
6  */
7 
8 #ifndef SRC_INCLUDE_SMASH_CLEBSCHGORDAN_LOOKUP_H_
9 #define SRC_INCLUDE_SMASH_CLEBSCHGORDAN_LOOKUP_H_
10 
11 #include <cassert>
12 #include <iostream>
13 #include <unordered_map>
14 
15 #include "smash/iomanipulators.h"
16 
17 namespace smash {
18 
19 /**
20  * Class to store and retrieve/calculate Clebsch-Gordan coefficients.
21  */
23  public:
24  /**
25  * Check in the Clebsch-Gordan lookup table if the requested coefficient is
26  * available. If so, return it, otherwise calculate the requested one, store
27  * it in the lookup table and return it.
28  *
29  * \see calculate_coefficient for a description of function arguments and
30  * return value.
31  */
32  static double coefficient(const int j_a, const int j_b, const int j_c,
33  const int m_a, const int m_b, const int m_c);
34 
35  /**
36  * Auxiliary struct to be used as key in the look up table of Clebsch-Gordan
37  * coefficients. It basically contains the input to retrieve one coefficient.
38  * Note that this is public since it is useful to be used from client code,
39  * e.g. in tests.
40  */
41  struct ThreeSpins {
42  int j1; ///< First isospin
43  int j2; ///< Second isospin
44  int j3; ///< Third isospin
45  int m1; ///< z component of first isospin
46  int m2; ///< z component of second isospin
47  int m3; ///< z component of third isospin
48 
49  private:
50  /**
51  * A utility function to avoid duplication in comparison operator(s).
52  * Note that in order to use \c auto deduced returned type, this member has
53  * to be defined before using it.
54  *
55  * @return A tuple of constant references to members.
56  */
57  auto tied() const { return std::tie(j1, j2, j3, m1, m2, m3); }
58 
59  public:
60  /**
61  * Comparison operator between two set of spin information. This is needed
62  * in order to use this object in a \c std::unordered_map container.
63  *
64  * @param other The object to be compared to
65  * @return \c true If all 6 spins value are identical
66  * @return \c false otherwise
67  */
68  bool operator==(const ThreeSpins &other) const {
69  return tied() == other.tied();
70  }
71  };
72 
73  private:
74  /**
75  * Calculate Clebsch-Gordan coefficient
76  * \f$(-1)^{j_a - j_b + m_c} \sqrt{(2 j_c + 1)} \cdot [Wigner 3J symbol] \f$
77  * \param[in] j_a isospin of first particle
78  * \param[in] j_b isospin of second particle
79  * \param[in] j_c isospin of resonance
80  * \param[in] m_a z-component of isospin of first particle
81  * \param[in] m_b z-component of isospin of second particle
82  * \param[in] m_c z-component of isospin of resonance
83  * \return Clebsch-Gordan coefficient for coupling of particles a, b and c
84  *
85  * Note that the calculation assumes that the isospin values (j/m) have been
86  * multiplied by two (in order to be integer).
87  */
88  static double calculate_coefficient(const int j_a, const int j_b,
89  const int j_c, const int m_a,
90  const int m_b, const int m_c);
91 
92  /**
93  * This is one of the possible ways to prepare a hashing mechanism to use a
94  * custom object in a \c std::unordered_map container. It has been preferred
95  * here to use a new \c struct instead of injecting a specialization into the
96  * \c std namespace, because we are here in the \c smash namespace and the
97  * object is going to be possibly better localized. Since the hash is not
98  * trivial, we also preferred this approach to using a lambda function to
99  * declare the hashing function.
100  */
101  struct ThreeSpinHash {
102  /**
103  * The overload of the \c operator() is the only needed ingredient to make
104  * this class ready to be used as hashing algorithm.
105  *
106  * Since there is not (yet) a C++ standard way of combining hashes, to
107  * implement functions like this one, it is necessary to choose a hashing
108  * algorithm. The algorithm should minimize the hash collision (different
109  * input giving the same output), but at the same time be as fast as
110  * possible. One could use boost library approach, which defines
111  * \code{.cpp}
112  * template <typename T>
113  * inline void hash_combine(std::size_t &seed, const T &val)
114  * {
115  * seed ^= std::hash<T>{}(val) + 0x9e3779b9 +
116  * (seed << 6) + (seed >> 2);
117  * }
118  * \endcode
119  * as function to combine hashes. Then this should be used here on each
120  * \c in member to produce the final hash. Although it has been tested to
121  * work, there is a simpler approach.
122  *
123  * -# It can be assumed (asserted in the code), that all \c ThreeSpins
124  * members are numbers smaller than 16 (in absolute value). Hence 5 bits
125  * are enough to represent them (numbers between -16 and 15). Using
126  * bit-shift operations, it is then possible to build a bit-sequence that
127  * in its last 6*5=30 bits contains the 6 integer information,
128  * appropriately "converted" to 5-bit sequences that are then
129  * concatenated.
130  * -# To get an \c int as a 5 bits integer into a \c std::size_t variable,
131  * one way is to cast to it first and then get rid of all but 5 rightmost
132  * bits. If e.g. the result variable is 64 bits large, this means to
133  * bit-shift to the left by 64-5=59 positions and then again to the right
134  * to bring the 5 bits back to the least significant positions (this is
135  * only an example, since the size of \c std::size_t is architecture
136  * dependent).
137  * -# Finally, once having the 6 5-bits integer as \c std::size_t variables,
138  * these can be combined shifting them to the left in a way that the 5
139  * relevant bits do not "overlap" (i.e. occupy different positions) and
140  * then summing them. In the implementation we use the minimum needed
141  * shift, i.e. having N=6 numbers we shift the first number by (N-1)*5,
142  * the second by (N-2)*5 and so on till the last one that does not get
143  * shifted. Since both the cast to "5-bits" numbers and the preparation
144  * of the sum involve bit-shifts, these are combined together.
145  *
146  * \note
147  * A couple of remarks worth noting:
148  * - Using \c std::bitset would probably make the code easier to read, but
149  * it has been benchmarked to be some % slower at low energies.
150  * - Yet another possibility would be to use mathematics/physics to come up
151  * with a map between the 6 integers and a super-index. This has been
152  * devised in \cite Rasch2004, where an algorithm to efficiently store
153  * Clebsch-Gordan coefficient is discussed. In it a way to map the three
154  * spins information to a single integer is proposed and this can be used
155  * here as hashing function, basically offering the guarantee that no hash
156  * collision will occur. However, the simpler hand-made hash discussed
157  * above and implemented in the following turned out to be more efficient
158  * with GNU compiler (equivalent with LLVM).
159  *
160  * @param in The spin information (meant to be used as input to be hashed)
161  * @return \c std::size_t The calculated hash value
162  */
163  std::size_t operator()(const ThreeSpins &in) const noexcept {
164  assert(in.j1 >= 0 && in.j1 < 16);
165  assert(in.j2 >= 0 && in.j2 < 16);
166  assert(in.j3 >= 0 && in.j3 < 16);
167  assert(std::abs(in.m1) < 16);
168  assert(std::abs(in.m2) < 16);
169  assert(std::abs(in.m3) < 16);
170  /*
171  * Although strictly speaking, this would only generate a very poor hash,
172  * we prefer making compilation fail if size_t has less than 32 bits.
173  * This is after all very unlikely and we'll deal with it only if needed.
174  */
175  static_assert(sizeof(std::size_t) >= 4);
176  // This is the amount to shift to obtain "5-bits numbers"
177  constexpr auto bitshift = sizeof(size_t) * 8 - 5;
178  // The different shift to the right make the 5-bit occupy different bits
179  return (static_cast<std::size_t>(in.j1) << bitshift >> (bitshift - 25)) +
180  (static_cast<std::size_t>(in.j2) << bitshift >> (bitshift - 20)) +
181  (static_cast<std::size_t>(in.j3) << bitshift >> (bitshift - 15)) +
182  (static_cast<std::size_t>(in.m1) << bitshift >> (bitshift - 10)) +
183  (static_cast<std::size_t>(in.m2) << bitshift >> (bitshift - 5)) +
184  (static_cast<std::size_t>(in.m3) << bitshift >> bitshift);
185  }
186  };
187 
188  /**
189  * Tabulation of Clebsch-Gordan coefficients. The C++ code to produce this
190  * member declaration can be found in the "tabulate" unit test of this file.
191  */
192  inline static std::unordered_map<ThreeSpins, double, ThreeSpinHash>
194  {{0, 0, 0, +0, +0, +0}, 1.00000000000000000},
195  {{0, 1, 1, +0, -1, -1}, 1.00000000000000022},
196  {{0, 1, 1, +0, +1, +1}, 1.00000000000000022},
197  {{0, 2, 2, +0, -2, -2}, 0.99999999999999989},
198  {{0, 2, 2, +0, +0, +0}, 0.99999999999999989},
199  {{0, 2, 2, +0, +2, +2}, 0.99999999999999989},
200  {{0, 3, 3, +0, -3, -3}, 1.00000000000000000},
201  {{0, 3, 3, +0, -1, -1}, 0.99999999999999989},
202  {{0, 3, 3, +0, +1, +1}, 0.99999999999999989},
203  {{0, 3, 3, +0, +3, +3}, 1.00000000000000000},
204  {{1, 0, 1, -1, +0, -1}, 1.00000000000000022},
205  {{1, 0, 1, +1, +0, +1}, 1.00000000000000022},
206  {{1, 1, 0, -1, +1, +0}, -0.70710678118654757},
207  {{1, 1, 0, +1, -1, +0}, 0.70710678118654757},
208  {{1, 1, 2, -1, -1, -2}, 0.99999999999999989},
209  {{1, 1, 2, -1, +1, +0}, 0.70710678118654746},
210  {{1, 1, 2, +1, -1, +0}, 0.70710678118654746},
211  {{1, 1, 2, +1, +1, +2}, 0.99999999999999989},
212  {{1, 2, 1, -1, +0, -1}, -0.57735026918962584},
213  {{1, 2, 1, -1, +2, +1}, -0.81649658092772615},
214  {{1, 2, 1, +1, -2, -1}, 0.81649658092772615},
215  {{1, 2, 1, +1, +0, +1}, 0.57735026918962584},
216  {{1, 2, 3, -1, -2, -3}, 1.00000000000000000},
217  {{1, 2, 3, -1, +0, -1}, 0.81649658092772615},
218  {{1, 2, 3, -1, +2, +1}, 0.57735026918962584},
219  {{1, 2, 3, +1, -2, -1}, 0.57735026918962584},
220  {{1, 2, 3, +1, +0, +1}, 0.81649658092772615},
221  {{1, 2, 3, +1, +2, +3}, 1.00000000000000000},
222  {{1, 3, 2, -1, -1, -2}, -0.49999999999999983},
223  {{1, 3, 2, -1, +1, +0}, -0.70710678118654724},
224  {{1, 3, 2, -1, +3, +2}, -0.86602540378443837},
225  {{1, 3, 2, +1, -3, -2}, 0.86602540378443837},
226  {{1, 3, 2, +1, -1, +0}, 0.70710678118654724},
227  {{1, 3, 2, +1, +1, +2}, 0.49999999999999983},
228  {{1, 3, 4, -1, -3, -4}, 1.00000000000000022},
229  {{1, 3, 4, -1, -1, -2}, 0.86602540378443871},
230  {{1, 3, 4, -1, +1, +0}, 0.70710678118654746},
231  {{1, 3, 4, -1, +3, +2}, 0.49999999999999994},
232  {{1, 3, 4, +1, -3, -2}, 0.49999999999999994},
233  {{1, 3, 4, +1, -1, +0}, 0.70710678118654746},
234  {{1, 3, 4, +1, +1, +2}, 0.86602540378443871},
235  {{1, 3, 4, +1, +3, +4}, 1.00000000000000022},
236  {{2, 0, 2, -2, +0, -2}, 0.99999999999999989},
237  {{2, 0, 2, +0, +0, +0}, 0.99999999999999989},
238  {{2, 0, 2, +2, +0, +2}, 0.99999999999999989},
239  {{2, 1, 1, -2, +1, -1}, -0.81649658092772615},
240  {{2, 1, 1, +0, -1, -1}, 0.57735026918962584},
241  {{2, 1, 1, +0, +1, +1}, -0.57735026918962584},
242  {{2, 1, 1, +2, -1, +1}, 0.81649658092772615},
243  {{2, 1, 3, -2, -1, -3}, 1.00000000000000000},
244  {{2, 1, 3, -2, +1, -1}, 0.57735026918962584},
245  {{2, 1, 3, +0, -1, -1}, 0.81649658092772615},
246  {{2, 1, 3, +0, +1, +1}, 0.81649658092772615},
247  {{2, 1, 3, +2, -1, +1}, 0.57735026918962584},
248  {{2, 1, 3, +2, +1, +3}, 1.00000000000000000},
249  {{2, 2, 0, -2, +2, +0}, 0.57735026918962584},
250  {{2, 2, 0, +0, +0, +0}, -0.57735026918962573},
251  {{2, 2, 0, +2, -2, +0}, 0.57735026918962584},
252  {{2, 2, 2, -2, +0, -2}, -0.70710678118654735},
253  {{2, 2, 2, -2, +2, +0}, -0.70710678118654735},
254  {{2, 2, 2, +0, -2, -2}, 0.70710678118654735},
255  {{2, 2, 2, +0, +2, +2}, -0.70710678118654735},
256  {{2, 2, 2, +2, -2, +0}, 0.70710678118654735},
257  {{2, 2, 2, +2, +0, +2}, 0.70710678118654735},
258  {{2, 2, 4, -2, -2, -4}, 1.00000000000000022},
259  {{2, 2, 4, -2, +0, -2}, 0.70710678118654746},
260  {{2, 2, 4, -2, +2, +0}, 0.40824829046386313},
261  {{2, 2, 4, +0, -2, -2}, 0.70710678118654746},
262  {{2, 2, 4, +0, +0, +0}, 0.81649658092772615},
263  {{2, 2, 4, +0, +2, +2}, 0.70710678118654746},
264  {{2, 2, 4, +2, -2, +0}, 0.40824829046386313},
265  {{2, 2, 4, +2, +0, +2}, 0.70710678118654746},
266  {{2, 2, 4, +2, +2, +4}, 1.00000000000000022},
267  {{2, 3, 1, -2, +1, -1}, 0.40824829046386302},
268  {{2, 3, 1, -2, +3, +1}, 0.70710678118654746},
269  {{2, 3, 1, +0, -1, -1}, -0.57735026918962573},
270  {{2, 3, 1, +0, +1, +1}, -0.57735026918962573},
271  {{2, 3, 1, +2, -3, -1}, 0.70710678118654746},
272  {{2, 3, 1, +2, -1, +1}, 0.40824829046386302},
273  {{2, 3, 3, -2, -1, -3}, -0.63245553203367610},
274  {{2, 3, 3, -2, +1, -1}, -0.73029674334022165},
275  {{2, 3, 3, -2, +3, +1}, -0.63245553203367610},
276  {{2, 3, 3, +0, -3, -3}, 0.77459666924148352},
277  {{2, 3, 3, +0, -1, -1}, 0.25819888974716126},
278  {{2, 3, 3, +0, +1, +1}, -0.25819888974716126},
279  {{2, 3, 3, +0, +3, +3}, -0.77459666924148352},
280  {{2, 3, 3, +2, -3, -1}, 0.63245553203367610},
281  {{2, 3, 3, +2, -1, +1}, 0.73029674334022165},
282  {{2, 3, 3, +2, +1, +3}, 0.63245553203367610},
283  {{2, 3, 5, -2, -3, -5}, 0.99999999999999989},
284  {{2, 3, 5, -2, -1, -3}, 0.77459666924148318},
285  {{2, 3, 5, -2, +1, -1}, 0.54772255750516596},
286  {{2, 3, 5, -2, +3, +1}, 0.31622776601683794},
287  {{2, 3, 5, +0, -3, -3}, 0.63245553203367599},
288  {{2, 3, 5, +0, -1, -1}, 0.77459666924148318},
289  {{2, 3, 5, +0, +1, +1}, 0.77459666924148318},
290  {{2, 3, 5, +0, +3, +3}, 0.63245553203367599},
291  {{2, 3, 5, +2, -3, -1}, 0.31622776601683794},
292  {{2, 3, 5, +2, -1, +1}, 0.54772255750516596},
293  {{2, 3, 5, +2, +1, +3}, 0.77459666924148318},
294  {{2, 3, 5, +2, +3, +5}, 0.99999999999999989},
295  {{3, 0, 3, -3, +0, -3}, 1.00000000000000000},
296  {{3, 0, 3, -1, +0, -1}, 0.99999999999999989},
297  {{3, 0, 3, +1, +0, +1}, 0.99999999999999989},
298  {{3, 0, 3, +3, +0, +3}, 1.00000000000000000},
299  {{3, 1, 2, -3, +1, -2}, -0.86602540378443837},
300  {{3, 1, 2, -1, -1, -2}, 0.49999999999999983},
301  {{3, 1, 2, -1, +1, +0}, -0.70710678118654724},
302  {{3, 1, 2, +1, -1, +0}, 0.70710678118654724},
303  {{3, 1, 2, +1, +1, +2}, -0.49999999999999983},
304  {{3, 1, 2, +3, -1, +2}, 0.86602540378443837},
305  {{3, 1, 4, -3, -1, -4}, 1.00000000000000022},
306  {{3, 1, 4, -3, +1, -2}, 0.49999999999999994},
307  {{3, 1, 4, -1, -1, -2}, 0.86602540378443871},
308  {{3, 1, 4, -1, +1, +0}, 0.70710678118654746},
309  {{3, 1, 4, +1, -1, +0}, 0.70710678118654746},
310  {{3, 1, 4, +1, +1, +2}, 0.86602540378443871},
311  {{3, 1, 4, +3, -1, +2}, 0.49999999999999994},
312  {{3, 1, 4, +3, +1, +4}, 1.00000000000000022},
313  {{3, 2, 1, -3, +2, -1}, 0.70710678118654746},
314  {{3, 2, 1, -1, +0, -1}, -0.57735026918962573},
315  {{3, 2, 1, -1, +2, +1}, 0.40824829046386302},
316  {{3, 2, 1, +1, -2, -1}, 0.40824829046386302},
317  {{3, 2, 1, +1, +0, +1}, -0.57735026918962573},
318  {{3, 2, 1, +3, -2, +1}, 0.70710678118654746},
319  {{3, 2, 3, -3, +0, -3}, -0.77459666924148352},
320  {{3, 2, 3, -3, +2, -1}, -0.63245553203367610},
321  {{3, 2, 3, -1, -2, -3}, 0.63245553203367610},
322  {{3, 2, 3, -1, +0, -1}, -0.25819888974716126},
323  {{3, 2, 3, -1, +2, +1}, -0.73029674334022165},
324  {{3, 2, 3, +1, -2, -1}, 0.73029674334022165},
325  {{3, 2, 3, +1, +0, +1}, 0.25819888974716126},
326  {{3, 2, 3, +1, +2, +3}, -0.63245553203367610},
327  {{3, 2, 3, +3, -2, +1}, 0.63245553203367610},
328  {{3, 2, 3, +3, +0, +3}, 0.77459666924148352},
329  {{3, 2, 5, -3, -2, -5}, 0.99999999999999989},
330  {{3, 2, 5, -3, +0, -3}, 0.63245553203367599},
331  {{3, 2, 5, -3, +2, -1}, 0.31622776601683794},
332  {{3, 2, 5, -1, -2, -3}, 0.77459666924148318},
333  {{3, 2, 5, -1, +0, -1}, 0.77459666924148318},
334  {{3, 2, 5, -1, +2, +1}, 0.54772255750516596},
335  {{3, 2, 5, +1, -2, -1}, 0.54772255750516596},
336  {{3, 2, 5, +1, +0, +1}, 0.77459666924148318},
337  {{3, 2, 5, +1, +2, +3}, 0.77459666924148318},
338  {{3, 2, 5, +3, -2, +1}, 0.31622776601683794},
339  {{3, 2, 5, +3, +0, +3}, 0.63245553203367599},
340  {{3, 2, 5, +3, +2, +5}, 0.99999999999999989},
341  {{3, 3, 0, -3, +3, +0}, -0.49999999999999994},
342  {{3, 3, 0, -1, +1, +0}, 0.49999999999999994},
343  {{3, 3, 0, +1, -1, +0}, -0.49999999999999994},
344  {{3, 3, 0, +3, -3, +0}, 0.49999999999999994},
345  {{3, 3, 2, -3, +1, -2}, 0.54772255750516596},
346  {{3, 3, 2, -3, +3, +0}, 0.67082039324993692},
347  {{3, 3, 2, -1, -1, -2}, -0.63245553203367599},
348  {{3, 3, 2, -1, +1, +0}, -0.22360679774997907},
349  {{3, 3, 2, -1, +3, +2}, 0.54772255750516596},
350  {{3, 3, 2, +1, -3, -2}, 0.54772255750516596},
351  {{3, 3, 2, +1, -1, +0}, -0.22360679774997907},
352  {{3, 3, 2, +1, +1, +2}, -0.63245553203367599},
353  {{3, 3, 2, +3, -3, +0}, 0.67082039324993692},
354  {{3, 3, 2, +3, -1, +2}, 0.54772255750516596},
355  {{3, 3, 4, -3, -1, -4}, -0.70710678118654746},
356  {{3, 3, 4, -3, +1, -2}, -0.70710678118654746},
357  {{3, 3, 4, -3, +3, +0}, -0.49999999999999994},
358  {{3, 3, 4, -1, -3, -4}, 0.70710678118654746},
359  {{3, 3, 4, -1, +1, +0}, -0.49999999999999994},
360  {{3, 3, 4, -1, +3, +2}, -0.70710678118654746},
361  {{3, 3, 4, +1, -3, -2}, 0.70710678118654746},
362  {{3, 3, 4, +1, -1, +0}, 0.49999999999999994},
363  {{3, 3, 4, +1, +3, +4}, -0.70710678118654746},
364  {{3, 3, 4, +3, -3, +0}, 0.49999999999999994},
365  {{3, 3, 4, +3, -1, +2}, 0.70710678118654746},
366  {{3, 3, 4, +3, +1, +4}, 0.70710678118654746},
367  {{3, 3, 6, -3, -3, -6}, 1.00000000000000022},
368  {{3, 3, 6, -3, -1, -4}, 0.70710678118654746},
369  {{3, 3, 6, -3, +1, -2}, 0.44721359549995793},
370  {{3, 3, 6, -3, +3, +0}, 0.22360679774997894},
371  {{3, 3, 6, -1, -3, -4}, 0.70710678118654746},
372  {{3, 3, 6, -1, -1, -2}, 0.77459666924148352},
373  {{3, 3, 6, -1, +1, +0}, 0.67082039324993670},
374  {{3, 3, 6, -1, +3, +2}, 0.44721359549995793},
375  {{3, 3, 6, +1, -3, -2}, 0.44721359549995793},
376  {{3, 3, 6, +1, -1, +0}, 0.67082039324993670},
377  {{3, 3, 6, +1, +1, +2}, 0.77459666924148352},
378  {{3, 3, 6, +1, +3, +4}, 0.70710678118654746},
379  {{3, 3, 6, +3, -3, +0}, 0.22360679774997894},
380  {{3, 3, 6, +3, -1, +2}, 0.44721359549995793},
381  {{3, 3, 6, +3, +1, +4}, 0.70710678118654746},
382  {{3, 3, 6, +3, +3, +6}, 1.00000000000000022},
383  };
384 };
385 
386 } // namespace smash
387 
388 #endif // SRC_INCLUDE_SMASH_CLEBSCHGORDAN_LOOKUP_H_
Class to store and retrieve/calculate Clebsch-Gordan coefficients.
static std::unordered_map< ThreeSpins, double, ThreeSpinHash > lookup_table
Tabulation of Clebsch-Gordan coefficients.
static double coefficient(const int j_a, const int j_b, const int j_c, const int m_a, const int m_b, const int m_c)
Check in the Clebsch-Gordan lookup table if the requested coefficient is available.
static double calculate_coefficient(const int j_a, const int j_b, const int j_c, const int m_a, const int m_b, const int m_c)
Calculate Clebsch-Gordan coefficient .
Definition: action.h:24
This is one of the possible ways to prepare a hashing mechanism to use a custom object in a std::unor...
std::size_t operator()(const ThreeSpins &in) const noexcept
The overload of the operator() is the only needed ingredient to make this class ready to be used as h...
Auxiliary struct to be used as key in the look up table of Clebsch-Gordan coefficients.
int m2
z component of second isospin
int m1
z component of first isospin
bool operator==(const ThreeSpins &other) const
Comparison operator between two set of spin information.
int m3
z component of third isospin
auto tied() const
A utility function to avoid duplication in comparison operator(s).