Eigen  5.0.1
 
Loading...
Searching...
No Matches
TriangularSolverMatrix.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2009 Gael Guennebaud <gael.guennebaud@inria.fr>
5// Modifications Copyright (C) 2022 Intel Corporation
6//
7// This Source Code Form is subject to the terms of the Mozilla
8// Public License v. 2.0. If a copy of the MPL was not distributed
9// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
10// SPDX-License-Identifier: MPL-2.0
11
12#ifndef EIGEN_TRIANGULAR_SOLVER_MATRIX_H
13#define EIGEN_TRIANGULAR_SOLVER_MATRIX_H
14
15// IWYU pragma: private
16#include "../InternalHeaderCheck.h"
17
18namespace Eigen {
19
20namespace internal {
21
22template <typename Scalar, typename Index, int Mode, bool Conjugate, int TriStorageOrder, int OtherInnerStride,
23 bool Specialized>
24struct trsmKernelL {
25 // Generic Implementation of triangular solve for triangular matrix on left and multiple rhs.
26 // Handles non-packed matrices.
27 //
28 // A lower-triangular panel is addressed from its top-left element with indices in [0, size);
29 // an upper-triangular one is addressed from its bottom-right element with indices in
30 // (-size, 0]. Both origins are elements of the panel, so callers never form a pointer outside
31 // the matrix they solve in. The AVX-512 specializations take both by the top-left element and
32 // convert when they delegate here.
33 static void kernel(Index size, Index otherSize, const Scalar* _tri, Index triStride, Scalar* _other, Index otherIncr,
34 Index otherStride);
35};
36
37template <typename Scalar, typename Index, int Mode, bool Conjugate, int TriStorageOrder, int OtherInnerStride,
38 bool Specialized>
39struct trsmKernelR {
40 // Generic Implementation of triangular solve for triangular matrix on right and multiple lhs.
41 // Handles non-packed matrices.
42 static void kernel(Index size, Index otherSize, const Scalar* _tri, Index triStride, Scalar* _other, Index otherIncr,
43 Index otherStride);
44};
45
46// The packet lanes are independent right-hand sides. Half the registers hold output accumulators,
47// enough independent multiply-add chains to cover the FMA latency on 16- and 32-register targets;
48// the rest hold the RHS packets and coefficient broadcasts.
49template <typename Scalar>
50struct triangular_solve_packet_traits {
51 static constexpr bool Enabled = packet_traits<Scalar>::Vectorizable &&
52 (std::is_same<Scalar, float>::value || std::is_same<Scalar, double>::value) &&
53 std::numeric_limits<Scalar>::is_iec559 && std::numeric_limits<Scalar>::radix == 2;
54 static constexpr int PacketSize = packet_traits<Scalar>::size;
55 static constexpr int RegisterRows = 4;
56 static constexpr int NumberOfRegisters = gebp_traits<Scalar, Scalar>::NumberOfRegisters;
57 static constexpr int RhsPackets = plain_enum_max(
58 1, plain_enum_min((NumberOfRegisters - 2) / (RegisterRows + 1), NumberOfRegisters / (2 * RegisterRows)));
59 // Rows solved per step for a tile of the given RHS packets: a narrow tile takes twice the rows while
60 // the extra accumulators fit in a full tile's registers, keeping more multiply-add chains in flight.
61 static constexpr int tile_rows(int packets) { return packets * 2 <= RhsPackets ? 2 * RegisterRows : RegisterRows; }
62 // Allocation bound, independent of the cache-dependent direct-solve cutoff.
63 static constexpr int WorkspaceRows = 128;
64#if defined(EIGEN_VECTORIZE_AVX512) && EIGEN_USE_AVX512_TRSM_L_KERNELS
65 static constexpr bool UseUnblocked = false;
66#else
67 static constexpr bool UseUnblocked = Enabled;
68#endif
69
70 template <typename Index>
71 static EIGEN_STRONG_INLINE bool use_unblocked(Index size, Index cols, std::ptrdiff_t l1) {
72 if (!UseUnblocked || size < RegisterRows || size > WorkspaceRows || cols < PacketSize) return false;
73 const int packets = cols >= RhsPackets * PacketSize ? RhsPackets : 1;
74 // Like GEBP's L1 depth model: the RHS tile, reciprocals, and RegisterRows rows
75 // of A are reused along k. Leave half of L1 for streaming and associativity.
76 const std::ptrdiff_t rowBytes = (packets * PacketSize + RegisterRows + 1) * sizeof(Scalar);
77 const std::ptrdiff_t transposeBytes = PacketSize * PacketSize * sizeof(Scalar);
78 return std::ptrdiff_t(size) * rowBytes + transposeBytes <= l1 / 2;
79 }
80};
81
82template <typename Scalar, typename Index, int Mode, int TriStorageOrder>
83struct triangular_solve_packet_kernel {
84 using Traits = triangular_solve_packet_traits<Scalar>;
85 using Packet = typename packet_traits<Scalar>::type;
86 using TriMapper = const_blas_data_mapper<Scalar, Index, TriStorageOrder>;
87 static constexpr int PacketSize = Traits::PacketSize;
88 static constexpr bool IsLower = (Mode & Lower) != 0;
89
90 template <int RhsPackets>
91 static EIGEN_STRONG_INLINE bool all_finite(const PacketBlock<Packet, RhsPackets>& x, bool_constant<true>) {
92 // Classify bits: fast-math may assume the arithmetic results are finite.
93 using FloatTraits = binary_floating_point_traits<Scalar>;
94 bool nonfinite = false;
95 for (int p = 0; p < RhsPackets; ++p) {
96 Scalar values[PacketSize];
97 pstoreu(values, x.packet[p]);
98 EIGEN_FAST_MATH_CONSTANT_BARRIER(values);
99 for (int c = 0; c < PacketSize; ++c)
100 nonfinite |= (FloatTraits::bits(values[c]) & FloatTraits::kExponentMask) == FloatTraits::kExponentMask;
101 }
102 return !nonfinite;
103 }
104
105 // Keep disabled scalar instantiations valid in C++14.
106 template <int RhsPackets>
107 static EIGEN_STRONG_INLINE bool all_finite(const PacketBlock<Packet, RhsPackets>&, bool_constant<false>) {
108 return true;
109 }
110
111 template <int RhsPackets>
112 static EIGEN_STRONG_INLINE void update(PacketBlock<Packet, RhsPackets>& x, const PacketBlock<Packet, RhsPackets>& y,
113 Scalar a) {
114 const Packet pa = pset1<Packet>(a);
115 for (int p = 0; p < RhsPackets; ++p) x.packet[p] = pnmadd(pa, y.packet[p], x.packet[p]);
116 }
117
118 template <int RhsPackets>
119 static EIGEN_STRONG_INLINE void scale(PacketBlock<Packet, RhsPackets>& x, Scalar a) {
120 EIGEN_IF_CONSTEXPR (!(Mode & UnitDiag)) {
121 const Packet pa = pset1<Packet>(a);
122 for (int p = 0; p < RhsPackets; ++p) x.packet[p] = pmul(x.packet[p], pa);
123 }
124 }
125
126 template <std::size_t Row, int RhsPackets, std::size_t... Next>
127 static EIGEN_STRONG_INLINE void solve_row(PacketBlock<Packet, RhsPackets>* x, const TriMapper& a,
128 const Scalar* inverse, Index r0, Index step, std::index_sequence<Next...>) {
129 const Index row = r0 + Index(Row) * step;
130 scale(x[Row], inverse[row]);
131 int unroll[] = {0, (update(x[Row + 1 + Next], x[Row], a(r0 + Index(Row + 1 + Next) * step, row)), 0)...};
132 EIGEN_UNUSED_VARIABLE(unroll);
133 }
134
135 // GCC generates extra instructions when the accumulator indices come from a loop.
136 template <int RhsPackets, std::size_t... Rows>
137 static EIGEN_STRONG_INLINE void solve_block(Index i, const TriMapper& a, const Scalar* inverse,
138 PacketBlock<Packet, RhsPackets>* work, Index r0, Index step,
139 std::index_sequence<Rows...>) {
140 PacketBlock<Packet, RhsPackets> x[] = {work[r0 + Index(Rows) * step]...};
141 for (Index k = 0; k < i; ++k) {
142 const Index c = IsLower ? k : r0 + i - k;
143 const PacketBlock<Packet, RhsPackets> y = work[c];
144 int unroll[] = {0, (update(x[Rows], y, a(r0 + Index(Rows) * step, c)), 0)...};
145 EIGEN_UNUSED_VARIABLE(unroll);
146 }
147 // The braced expansion orders the dependent solves by row.
148 int solve_rows[] = {
149 0, (solve_row<Rows>(x, a, inverse, r0, step, std::make_index_sequence<sizeof...(Rows) - Rows - 1>{}), 0)...};
150 EIGEN_UNUSED_VARIABLE(solve_rows);
151 int store_rows[] = {0, (work[r0 + Index(Rows) * step] = x[Rows], 0)...};
152 EIGEN_UNUSED_VARIABLE(store_rows);
153 }
154
155 template <int RhsPackets>
156 static EIGEN_STRONG_INLINE void solve(Index size, const TriMapper& a, const Scalar* inverse, Scalar* other,
157 Index otherStride) {
158 PacketBlock<Packet, RhsPackets> work[Traits::WorkspaceRows];
159 for (int p = 0; p < RhsPackets; ++p) {
160 Index row = 0;
161 for (; row + PacketSize <= size; row += PacketSize) {
162 PacketBlock<Packet, PacketSize> block;
163 for (int c = 0; c < PacketSize; ++c)
164 block.packet[c] = ploadu<Packet>(other + row + (p * PacketSize + c) * otherStride);
165 ptranspose(block);
166 for (int r = 0; r < PacketSize; ++r) work[row + r].packet[p] = block.packet[r];
167 }
168 for (; row < size; ++row)
169 work[row].packet[p] = pgather<Scalar, Packet>(other + row + p * PacketSize * otherStride, otherStride);
170 }
171 Index i = 0;
172 const Index step = IsLower ? 1 : -1;
173 constexpr int TileRows = Traits::tile_rows(RhsPackets);
174 EIGEN_IF_CONSTEXPR (TileRows != Traits::RegisterRows) {
175 for (; i + TileRows <= size; i += TileRows)
176 solve_block(i, a, inverse, work, IsLower ? i : size - i - 1, step, std::make_index_sequence<TileRows>{});
177 }
178 for (; i + Traits::RegisterRows <= size; i += Traits::RegisterRows) {
179 const Index r0 = IsLower ? i : size - i - 1;
180 // Preserve the four-row schedule that keeps GCC and Clang's hot loops compact.
181 EIGEN_IF_CONSTEXPR (Traits::RegisterRows == 4) {
182 const Index r1 = r0 + step, r2 = r1 + step, r3 = r2 + step;
183 PacketBlock<Packet, RhsPackets> x0 = work[r0], x1 = work[r1], x2 = work[r2], x3 = work[r3];
184 for (Index k = 0; k < i; ++k) {
185 const Index c = IsLower ? k : size - k - 1;
186 const PacketBlock<Packet, RhsPackets> y = work[c];
187 update(x0, y, a(r0, c));
188 update(x1, y, a(r1, c));
189 update(x2, y, a(r2, c));
190 update(x3, y, a(r3, c));
191 }
192 scale(x0, inverse[r0]);
193 update(x1, x0, a(r1, r0));
194 update(x2, x0, a(r2, r0));
195 update(x3, x0, a(r3, r0));
196 scale(x1, inverse[r1]);
197 update(x2, x1, a(r2, r1));
198 update(x3, x1, a(r3, r1));
199 scale(x2, inverse[r2]);
200 update(x3, x2, a(r3, r2));
201 scale(x3, inverse[r3]);
202 work[r0] = x0;
203 work[r1] = x1;
204 work[r2] = x2;
205 work[r3] = x3;
206 } else {
207 solve_block(i, a, inverse, work, r0, step, std::make_index_sequence<Traits::RegisterRows>{});
208 }
209 }
210 for (; i < size; ++i) {
211 const Index r = IsLower ? i : size - i - 1;
212 PacketBlock<Packet, RhsPackets> x = work[r];
213 for (Index k = 0; k < i; ++k) {
214 const Index c = IsLower ? k : size - k - 1;
215 update(x, work[c], a(r, c));
216 }
217 scale(x, inverse[r]);
218 work[r] = x;
219 }
220 EIGEN_IF_CONSTEXPR (TriStorageOrder == RowMajor) {
221 // Successive RHS updates can overflow before cancellation in a row's dot product.
222 // Every later row uses all solved rows, propagating nonfinite lanes to the final row.
223 // Retry with the original accumulation order before overwriting any RHS coefficient.
224 if (!all_finite(work[IsLower ? size - 1 : 0], bool_constant<Traits::Enabled>{})) {
225 const Index origin = IsLower ? 0 : size - 1;
226 trsmKernelL<Scalar, Index, Mode, false, TriStorageOrder, 1, false>::kernel(
227 size, Index(RhsPackets * PacketSize), &a(origin, origin), a.stride(), other + origin, Index(1),
228 otherStride);
229 return;
230 }
231 }
232 for (int p = 0; p < RhsPackets; ++p) {
233 Index row = 0;
234 for (; row + PacketSize <= size; row += PacketSize) {
235 PacketBlock<Packet, PacketSize> block;
236 for (int r = 0; r < PacketSize; ++r) block.packet[r] = work[row + r].packet[p];
237 ptranspose(block);
238 for (int c = 0; c < PacketSize; ++c) pstoreu(other + row + (p * PacketSize + c) * otherStride, block.packet[c]);
239 }
240 for (; row < size; ++row)
241 pscatter<Scalar, Packet>(other + row + p * PacketSize * otherStride, work[row].packet[p], otherStride);
242 }
243 }
244
245 // The last cols < PacketSize columns, zero-padded to a packet in a copy. For a row-major triangle this costs
246 // one packet solve instead of a scalar dot product per coefficient; a column-major triangle's scalar solve
247 // is a contiguous axpy per column, which compilers vectorize. Padded lanes are dropped. Inlined, its buffer
248 // and second solve<1> copy measured slower even for solves that never pad.
249 static EIGEN_DONT_INLINE void solve_padded(Index size, const TriMapper& a, const Scalar* inverse, Scalar* other,
250 Index otherStride, Index cols) {
251 Map<Matrix<Scalar, Dynamic, Dynamic, ColMajor>, Unaligned, OuterStride<>> rest(other, size, cols,
252 OuterStride<>(otherStride));
253 Matrix<Scalar, Dynamic, Dynamic, ColMajor, Traits::WorkspaceRows, PacketSize> padded(size, Index(PacketSize));
254 padded.leftCols(cols) = rest;
255 padded.rightCols(PacketSize - cols).setZero();
256 solve<1>(size, a, inverse, padded.data(), size);
257 rest = padded.leftCols(cols);
258 }
259
260 static EIGEN_DONT_INLINE void kernel(Index size, Index cols, const Scalar* tri, Index triStride, Scalar* other,
261 Index otherStride) {
262 eigen_internal_assert(size <= Traits::WorkspaceRows && cols >= PacketSize &&
263 (TriStorageOrder == RowMajor || cols % PacketSize == 0));
264 EIGEN_IF_CONSTEXPR (!IsLower) {
265 tri -= (size - 1) * (triStride + 1);
266 other -= size - 1;
267 }
268 TriMapper a(tri, triStride);
269 Scalar inverse[Traits::WorkspaceRows];
270 Map<Vector<Scalar, Dynamic>> mapped(inverse, size);
271 EIGEN_IF_CONSTEXPR (Mode & UnitDiag) {
272 mapped.setOnes();
273 } else {
274 const Map<const Vector<Scalar, Dynamic>, Unaligned, InnerStride<Dynamic>> diagonal(
275 tri, size, InnerStride<Dynamic>(triStride + 1));
276 mapped = diagonal.cwiseInverse();
277 }
278 Index j = 0;
279 EIGEN_IF_CONSTEXPR (Traits::RhsPackets > 1) {
280 for (; j + Traits::RhsPackets * PacketSize <= cols; j += Traits::RhsPackets * PacketSize)
281 solve<Traits::RhsPackets>(size, a, inverse, other + j * otherStride, otherStride);
282 }
283 EIGEN_IF_CONSTEXPR (Traits::RhsPackets > 2) {
284 for (; j + 2 * PacketSize <= cols; j += 2 * PacketSize)
285 solve<2>(size, a, inverse, other + j * otherStride, otherStride);
286 }
287 for (; j + PacketSize <= cols; j += PacketSize) solve<1>(size, a, inverse, other + j * otherStride, otherStride);
288 EIGEN_IF_CONSTEXPR (TriStorageOrder == RowMajor) {
289 if (j < cols) solve_padded(size, a, inverse, other + j * otherStride, otherStride, cols - j);
290 }
291 }
292};
293
294template <typename Scalar, typename Index, int Mode, bool Conjugate, int TriStorageOrder, int OtherInnerStride,
295 bool Specialized>
296EIGEN_STRONG_INLINE void trsmKernelL<Scalar, Index, Mode, Conjugate, TriStorageOrder, OtherInnerStride,
297 Specialized>::kernel(Index size, Index otherSize, const Scalar* _tri,
298 Index triStride, Scalar* _other, Index otherIncr,
299 Index otherStride) {
300 EIGEN_IF_CONSTEXPR ((Specialized && OtherInnerStride == 1 && triangular_solve_packet_traits<Scalar>::Enabled)) {
301 if (size >= triangular_solve_packet_traits<Scalar>::RegisterRows &&
302 size <= triangular_solve_packet_traits<Scalar>::WorkspaceRows && otherSize >= packet_traits<Scalar>::size) {
303 // The packet kernel pads the last columns only for a row-major triangle; see solve_padded.
304 const Index packetCols =
305 TriStorageOrder == RowMajor ? otherSize : numext::round_down(otherSize, Index(packet_traits<Scalar>::size));
306 triangular_solve_packet_kernel<Scalar, Index, Mode, TriStorageOrder>::kernel(size, packetCols, _tri, triStride,
307 _other, otherStride);
308 if (packetCols == otherSize) return;
309 otherSize -= packetCols;
310 _other += packetCols * otherStride;
311 }
312 }
313 using TriMapper = const_blas_data_mapper<Scalar, Index, TriStorageOrder>;
314 using OtherMapper = blas_data_mapper<Scalar, Index, ColMajor, Unaligned, OtherInnerStride>;
315 TriMapper tri(_tri, triStride);
316 OtherMapper other(_other, otherStride, otherIncr);
317
318 enum { IsLower = (Mode & Lower) == Lower };
319 conj_if<Conjugate> conj;
320
321 // tr solve
322 for (Index k = 0; k < size; ++k) {
323 // TODO: write a small kernel handling this (can be shared with trsv)
324 Index i = IsLower ? k : -k;
325 Index rs = size - k - 1; // remaining size
326 Index s = TriStorageOrder == RowMajor ? (IsLower ? 0 : i + 1) : IsLower ? i + 1 : i - rs;
327
328 Scalar a = (Mode & UnitDiag) ? Scalar(1) : Scalar(Scalar(1) / conj(tri(i, i)));
329 for (Index j = 0; j < otherSize; ++j) {
330 EIGEN_IF_CONSTEXPR (TriStorageOrder == RowMajor) {
331 Scalar b(0);
332 const Scalar* l = &tri(i, s);
333 typename OtherMapper::LinearMapper r = other.getLinearMapper(s, j);
334 for (Index i3 = 0; i3 < k; ++i3) b += conj(l[i3]) * r(i3);
335
336 other(i, j) = (other(i, j) - b) * a;
337 } else {
338 Scalar& otherij = other(i, j);
339 otherij *= a;
340 Scalar b = otherij;
341 typename OtherMapper::LinearMapper r = other.getLinearMapper(s, j);
342 typename TriMapper::LinearMapper l = tri.getLinearMapper(s, i);
343 for (Index i3 = 0; i3 < rs; ++i3) r(i3) -= b * conj(l(i3));
344 }
345 }
346 }
347}
348
349template <typename Scalar, typename Index, int Mode, bool Conjugate, int TriStorageOrder, int OtherInnerStride,
350 bool Specialized>
351EIGEN_STRONG_INLINE void trsmKernelR<Scalar, Index, Mode, Conjugate, TriStorageOrder, OtherInnerStride,
352 Specialized>::kernel(Index size, Index otherSize, const Scalar* _tri,
353 Index triStride, Scalar* _other, Index otherIncr,
354 Index otherStride) {
355 using RealScalar = typename NumTraits<Scalar>::Real;
356 using LhsMapper = blas_data_mapper<Scalar, Index, ColMajor, Unaligned, OtherInnerStride>;
357 using RhsMapper = const_blas_data_mapper<Scalar, Index, TriStorageOrder>;
358 LhsMapper lhs(_other, otherStride, otherIncr);
359 RhsMapper rhs(_tri, triStride);
360
361 enum { IsLower = (Mode & Lower) == Lower };
362 conj_if<Conjugate> conj;
363
364 for (Index k = 0; k < size; ++k) {
365 Index j = IsLower ? size - k - 1 : k;
366
367 typename LhsMapper::LinearMapper r = lhs.getLinearMapper(0, j);
368 EIGEN_IF_CONSTEXPR (OtherInnerStride == 1 && packet_traits<Scalar>::Vectorizable) {
369 using Packet = typename packet_traits<Scalar>::type;
370 constexpr Index PS = unpacket_traits<Packet>::size;
371 // Unrolled k3 loop by 4 to reduce r load/store traffic.
372 Index k3 = 0;
373 for (; k3 + 3 < k; k3 += 4) {
374 Index col0 = IsLower ? j + 1 + k3 : k3;
375 Scalar b0 = conj(rhs(col0, j));
376 Scalar b1 = conj(rhs(col0 + 1, j));
377 Scalar b2 = conj(rhs(col0 + 2, j));
378 Scalar b3 = conj(rhs(col0 + 3, j));
379 Packet neg_pb0 = pset1<Packet>(-b0);
380 Packet neg_pb1 = pset1<Packet>(-b1);
381 Packet neg_pb2 = pset1<Packet>(-b2);
382 Packet neg_pb3 = pset1<Packet>(-b3);
383 typename LhsMapper::LinearMapper a0 = lhs.getLinearMapper(0, col0);
384 typename LhsMapper::LinearMapper a1 = lhs.getLinearMapper(0, col0 + 1);
385 typename LhsMapper::LinearMapper a2 = lhs.getLinearMapper(0, col0 + 2);
386 typename LhsMapper::LinearMapper a3 = lhs.getLinearMapper(0, col0 + 3);
387 Index i = 0;
388 for (; i + PS <= otherSize; i += PS) {
389 Packet pr = r.template loadPacket<Packet>(i);
390 pr = pmadd(a0.template loadPacket<Packet>(i), neg_pb0, pr);
391 pr = pmadd(a1.template loadPacket<Packet>(i), neg_pb1, pr);
392 pr = pmadd(a2.template loadPacket<Packet>(i), neg_pb2, pr);
393 pr = pmadd(a3.template loadPacket<Packet>(i), neg_pb3, pr);
394 r.template storePacket<Packet>(i, pr);
395 }
396 for (; i < otherSize; ++i) {
397 r(i) -= a0(i) * b0 + a1(i) * b1 + a2(i) * b2 + a3(i) * b3;
398 }
399 }
400 // Handle remaining k3 iterations with vectorized inner loop.
401 for (; k3 < k; ++k3) {
402 Scalar b = conj(rhs(IsLower ? j + 1 + k3 : k3, j));
403 typename LhsMapper::LinearMapper a = lhs.getLinearMapper(0, IsLower ? j + 1 + k3 : k3);
404 Packet neg_pb = pset1<Packet>(-b);
405 Index i = 0;
406 for (; i + PS <= otherSize; i += PS) {
407 Packet pr = r.template loadPacket<Packet>(i);
408 pr = pmadd(a.template loadPacket<Packet>(i), neg_pb, pr);
409 r.template storePacket<Packet>(i, pr);
410 }
411 for (; i < otherSize; ++i) r(i) -= a(i) * b;
412 }
413 // Vectorized diagonal scaling.
414 EIGEN_IF_CONSTEXPR ((Mode & UnitDiag) == 0) {
415 Scalar inv_rjj = RealScalar(1) / conj(rhs(j, j));
416 Packet pinv = pset1<Packet>(inv_rjj);
417 Index i = 0;
418 for (; i + PS <= otherSize; i += PS) {
419 r.template storePacket<Packet>(i, pmul(r.template loadPacket<Packet>(i), pinv));
420 }
421 for (; i < otherSize; ++i) r(i) *= inv_rjj;
422 }
423 } else {
424 for (Index k3 = 0; k3 < k; ++k3) {
425 Scalar b = conj(rhs(IsLower ? j + 1 + k3 : k3, j));
426 typename LhsMapper::LinearMapper a = lhs.getLinearMapper(0, IsLower ? j + 1 + k3 : k3);
427 for (Index i = 0; i < otherSize; ++i) r(i) -= a(i) * b;
428 }
429 EIGEN_IF_CONSTEXPR ((Mode & UnitDiag) == 0) {
430 Scalar inv_rjj = RealScalar(1) / conj(rhs(j, j));
431 for (Index i = 0; i < otherSize; ++i) r(i) *= inv_rjj;
432 }
433 }
434 }
435}
436
437// if the rhs is row major, let's transpose the product
438template <typename Scalar, typename Index, int Side, int Mode, bool Conjugate, int TriStorageOrder,
439 int OtherInnerStride>
440struct triangular_solve_matrix<Scalar, Index, Side, Mode, Conjugate, TriStorageOrder, RowMajor, OtherInnerStride> {
441 static void run(Index size, Index cols, const Scalar* tri, Index triStride, Scalar* _other, Index otherIncr,
442 Index otherStride, level3_blocking<Scalar, Scalar>& blocking) {
443 triangular_solve_matrix<
444 Scalar, Index, Side == OnTheLeft ? OnTheRight : OnTheLeft, (Mode & UnitDiag) | ((Mode & Upper) ? Lower : Upper),
445 NumTraits<Scalar>::IsComplex && Conjugate, TriStorageOrder == RowMajor ? ColMajor : RowMajor, ColMajor,
446 OtherInnerStride>::run(size, cols, tri, triStride, _other, otherIncr, otherStride, blocking);
447 }
448};
449
453template <typename Scalar>
454std::ptrdiff_t triangular_solve_budget(std::ptrdiff_t l2, std::ptrdiff_t l3) {
455 return (numext::maxi)(l3 / 4, l2) / std::ptrdiff_t(sizeof(Scalar));
456}
457
462template <typename Index>
463Index triangular_solve_panel_columns(Index size, Index cols, std::ptrdiff_t budget, Index nr) {
464 eigen_internal_assert(size > 0);
465 const std::ptrdiff_t width = numext::round_down<std::ptrdiff_t>(budget / size, nr);
466 return width >= 512 && width < cols ? Index(width) : cols;
467}
468
479template <typename Scalar, typename Index>
480Index triangular_solve_kc(Index size, Index otherSize, Index extent, std::ptrdiff_t budget, bool slabRuns,
481 level3_blocking<Scalar, Scalar>& blocking) {
482 EIGEN_UNUSED_VARIABLE(extent);
483 const bool deep = std::ptrdiff_t(size) * otherSize > budget || (slabRuns && std::ptrdiff_t(size) * size / 2 > budget);
484 if (!deep || blocking.blockA() != nullptr) return blocking.kc();
485 Index kc = size, mc = size, nc = otherSize;
486 computeProductBlockingSizes<Scalar, Scalar>(kc, mc, nc);
487 kc = (numext::mini)(kc, numext::round_down((numext::mini)(size / 8, Index(160)), Index(8)));
488#if defined(EIGEN_ALLOCA) && !defined(EIGEN_NO_ALLOCA)
489 const std::ptrdiff_t stackKc =
490 std::ptrdiff_t(EIGEN_STACK_ALLOCATION_LIMIT) / (std::ptrdiff_t(sizeof(Scalar)) * extent);
491 if (blocking.kc() <= stackKc) kc = Index((numext::mini)(std::ptrdiff_t(kc), stackKc));
492#endif
493 return (numext::maxi)(kc, blocking.kc());
494}
495
496/* Optimized triangular solver with multiple right hand side and the triangular matrix on the left
497 */
498template <typename Scalar, typename Index, int Mode, bool Conjugate, int TriStorageOrder, int OtherInnerStride>
499struct triangular_solve_matrix<Scalar, Index, OnTheLeft, Mode, Conjugate, TriStorageOrder, ColMajor, OtherInnerStride> {
500 static EIGEN_DONT_INLINE void run(Index size, Index otherSize, const Scalar* _tri, Index triStride, Scalar* _other,
501 Index otherIncr, Index otherStride, level3_blocking<Scalar, Scalar>& blocking);
502};
503
504template <typename Scalar, typename Index, int Mode, bool Conjugate, int TriStorageOrder, int OtherInnerStride>
505EIGEN_DONT_INLINE void triangular_solve_matrix<Scalar, Index, OnTheLeft, Mode, Conjugate, TriStorageOrder, ColMajor,
506 OtherInnerStride>::run(Index size, Index otherSize, const Scalar* _tri,
507 Index triStride, Scalar* _other, Index otherIncr,
508 Index otherStride,
509 level3_blocking<Scalar, Scalar>& blocking) {
510 std::ptrdiff_t l1, l2, l3;
511 manage_caching_sizes(GetAction, &l1, &l2, &l3);
512 EIGEN_IF_CONSTEXPR ((OtherInnerStride == 1 && triangular_solve_packet_traits<Scalar>::Enabled)) {
513 using PacketTraits = triangular_solve_packet_traits<Scalar>;
514 if (PacketTraits::use_unblocked(size, otherSize, l1)) {
515 const Index origin = (Mode & Lower) ? 0 : size - 1;
516 trsmKernelL<Scalar, Index, Mode, Conjugate, TriStorageOrder, OtherInnerStride, true>::kernel(
517 size, otherSize, _tri + origin * (triStride + 1), triStride, _other + origin, otherIncr, otherStride);
518 return;
519 }
520 }
521#if defined(EIGEN_VECTORIZE_AVX512) && defined(EIGEN_USE_AVX512_TRSM_L_KERNELS) && EIGEN_USE_AVX512_TRSM_L_KERNELS && \
522 EIGEN_ENABLE_AVX512_NOCOPY_TRSM_L_CUTOFFS
523 EIGEN_IF_CONSTEXPR ((OtherInnerStride == 1 &&
524 (std::is_same<Scalar, float>::value || std::is_same<Scalar, double>::value))) {
525 // Very rough cutoffs to determine when to call trsm w/o packing
526 // For small problem sizes trsmKernel compiled with clang is generally faster.
527 // TODO: Investigate better heuristics for cutoffs.
528 double L2Cap = 0.5; // 50% of L2 size
529 if (size < avx512_trsm_cutoff<Scalar>(l2, otherSize, L2Cap)) {
530 trsmKernelL<Scalar, Index, Mode, Conjugate, TriStorageOrder, 1, /*Specialized=*/true>::kernel(
531 size, otherSize, _tri, triStride, _other, 1, otherStride);
532 return;
533 }
534 }
535#endif
536
537 using TriMapper = const_blas_data_mapper<Scalar, Index, TriStorageOrder>;
538 using OtherMapper = blas_data_mapper<Scalar, Index, ColMajor, Unaligned, OtherInnerStride>;
539 TriMapper tri(_tri, triStride);
540
541 using Traits = gebp_traits<Scalar, Scalar>;
542
543 enum { SmallPanelWidth = plain_enum_max(Traits::mr, Traits::nr), IsLower = (Mode & Lower) == Lower };
544
545 // Every k-block updates the rows of the right-hand side beyond it through gebp, so a right-hand side
546 // solved whole streams through the caches size/kc times (issue #3162); column panels that stay in
547 // cache keep those sweeps out of memory. This kernel packs the slabs of the triangle by rows, which
548 // costs more the deeper they are, so a large triangle alone does not deepen it.
549 const std::ptrdiff_t budget = triangular_solve_budget<Scalar>(l2, l3);
550 const Index nc = triangular_solve_panel_columns(size, otherSize, budget, Index(Traits::nr));
551 const Index mc = (numext::mini)(size, blocking.mc()); // cache block size along the M direction
552 // The tr solve below packs up to panelWidth x kc entries of the triangle into blockA, where panelWidth is
553 // SmallPanelWidth or, with the packet kernel, at most blockARows.
554 const Index blockARows = (numext::maxi)(mc, Index(SmallPanelWidth));
555 // cache block size along the K direction
556 const Index kc = triangular_solve_kc<Scalar>(size, otherSize, (numext::maxi)(blockARows, nc), budget,
557 /*slabRuns=*/false, blocking);
558
559 std::size_t sizeA = kc * blockARows;
560 std::size_t sizeB = kc * nc;
561
562 ei_declare_aligned_stack_constructed_variable(Scalar, blockA, sizeA, blocking.blockA());
563 ei_declare_aligned_stack_constructed_variable(Scalar, blockB, sizeB, blocking.blockB());
564
565 gebp_kernel<Scalar, Scalar, Index, OtherMapper, Traits::mr, Traits::nr, Conjugate, false> gebp_kernel;
566 gemm_pack_lhs<Scalar, Index, TriMapper, Traits::mr, Traits::LhsProgress, typename Traits::LhsPacket4Packing,
567 TriStorageOrder>
568 pack_lhs;
569 gemm_pack_rhs<Scalar, Index, OtherMapper, Traits::nr, ColMajor, false, true> pack_rhs;
570
571 // the goal here is to subdivide the Rhs panels such that we keep some cache
572 // coherence when accessing the rhs elements
573 Index subcols = otherSize > 0 ? l2 / (4 * sizeof(Scalar) * numext::maxi<Index>(otherStride, size)) : 0;
574 Index colStep = Traits::nr;
575 // Width of the diagonal panels solved between gebp updates of their depth.
576 Index panelWidth = SmallPanelWidth;
577 EIGEN_IF_CONSTEXPR ((OtherInnerStride == 1 && triangular_solve_packet_traits<Scalar>::UseUnblocked)) {
578 // The packet kernel solves a whole diagonal block faster than narrow panels do, since those
579 // interleave slower rank-SmallPanelWidth gebp updates; a block deeper than its workspace is split
580 // evenly. That needs a tile of two packets of right-hand sides where the registers allow: a single
581 // packet measured slower. pack_rhs reads each kc x subcols chunk right after its solve, so a chunk should
582 // stay in L2; packet-aligned chunks confine any column tail to an Rhs panel's last chunk.
583 using PacketTraits = triangular_solve_packet_traits<Scalar>;
584 const Index rows = Index(PacketTraits::RegisterRows), packet = Index(PacketTraits::PacketSize);
585 if (otherSize >= Index(plain_enum_min(2, PacketTraits::RhsPackets)) * packet) {
586 const Index panels = numext::div_ceil(kc, Index(PacketTraits::WorkspaceRows));
587 panelWidth = (numext::mini)(blockARows, rows * numext::div_ceil(numext::div_ceil(kc, panels), rows));
588 colStep = colStep % packet == 0 ? colStep : packet % colStep == 0 ? packet : colStep * packet;
589 subcols = Index(l2 / (2 * std::ptrdiff_t(sizeof(Scalar)) * kc));
590 }
591 }
592 subcols = numext::maxi<Index>(numext::round_down(subcols, colStep), colStep);
593
594 for (Index j0 = 0; j0 < otherSize; j0 += nc) {
595 const Index cols = (numext::mini)(otherSize - j0, nc);
596 Scalar* _panel = _other + j0 * otherStride;
597 OtherMapper other(_panel, otherStride, otherIncr);
598
599 for (Index k2 = IsLower ? 0 : size; IsLower ? k2 < size : k2 > 0; IsLower ? k2 += kc : k2 -= kc) {
600 const Index actual_kc = (numext::mini)(IsLower ? size - k2 : k2, kc);
601
602 // We have selected and packed a big horizontal panel R1 of rhs. Let B be the packed copy of this panel,
603 // and R2 the remaining part of rhs. The corresponding vertical panel of lhs is split into
604 // A11 (the triangular part) and A21 the remaining rectangular part.
605 // Then the high level algorithm is:
606 // - B = R1 => general block copy (done during the next step)
607 // - R1 = A11^-1 B => tricky part
608 // - update B from the new R1 => actually this has to be performed continuously during the above step
609 // - R2 -= A21 * B => GEPP
610
611 // The tricky part: compute R1 = A11^-1 B while updating B from R1
612 // The idea is to split A11 into multiple small vertical panels.
613 // Each panel can be split into a small triangular part T1k which is processed without optimization,
614 // and the remaining small part T2k which is processed using gebp with appropriate block strides
615 for (Index j2 = 0; j2 < cols; j2 += subcols) {
616 Index actual_cols = (numext::mini)(cols - j2, subcols);
617 // for each small vertical panels [T1k^T, T2k^T]^T of lhs
618 for (Index k1 = 0; k1 < actual_kc; k1 += panelWidth) {
619 Index actualPanelWidth = numext::mini<Index>(actual_kc - k1, panelWidth);
620 // tr solve
621 {
622 Index i = IsLower ? k2 + k1 : k2 - k1 - 1;
623#if defined(EIGEN_VECTORIZE_AVX512) && defined(EIGEN_USE_AVX512_TRSM_L_KERNELS) && EIGEN_USE_AVX512_TRSM_L_KERNELS
624 EIGEN_IF_CONSTEXPR ((OtherInnerStride == 1 &&
625 (std::is_same<Scalar, float>::value || std::is_same<Scalar, double>::value))) {
626 i = IsLower ? k2 + k1 : k2 - k1 - actualPanelWidth;
627 }
628#endif
629 using DiagonalKernel =
630 trsmKernelL<Scalar, Index, Mode, Conjugate, TriStorageOrder, OtherInnerStride, /*Specialized=*/true>;
631 const Scalar* diagonal = _tri + i + i * triStride;
632 Scalar* rhs = _panel + i * otherIncr + j2 * otherStride;
633 // Narrow panels keep their compile-time width bound, which lets compilers shorten the scalar
634 // kernel's loops; the packet kernel solves wider ones.
635 if (panelWidth == Index(SmallPanelWidth))
636 DiagonalKernel::kernel(numext::mini<Index>(actualPanelWidth, SmallPanelWidth), actual_cols, diagonal,
637 triStride, rhs, otherIncr, otherStride);
638 else
639 DiagonalKernel::kernel(actualPanelWidth, actual_cols, diagonal, triStride, rhs, otherIncr, otherStride);
640 }
641
642 Index lengthTarget = actual_kc - k1 - actualPanelWidth;
643 Index startBlock = IsLower ? k2 + k1 : k2 - k1 - actualPanelWidth;
644 Index blockBOffset = IsLower ? k1 : lengthTarget;
645
646 // update the respective rows of B from other
647 pack_rhs(blockB + actual_kc * j2, other.getSubMapper(startBlock, j2), actualPanelWidth, actual_cols,
648 actual_kc, blockBOffset);
649
650 // GEBP
651 if (lengthTarget > 0) {
652 Index startTarget = IsLower ? k2 + k1 + actualPanelWidth : k2 - actual_kc;
653
654 pack_lhs(blockA, tri.getSubMapper(startTarget, startBlock), actualPanelWidth, lengthTarget);
655
656 gebp_kernel(other.getSubMapper(startTarget, j2), blockA, blockB + actual_kc * j2, lengthTarget,
657 actualPanelWidth, actual_cols, Scalar(-1), actualPanelWidth, actual_kc, 0, blockBOffset);
658 }
659 }
660 }
661
662 // R2 -= A21 * B => GEPP
663 {
664 Index start = IsLower ? k2 + kc : 0;
665 Index end = IsLower ? size : k2 - kc;
666 for (Index i2 = start; i2 < end; i2 += mc) {
667 const Index actual_mc = (numext::mini)(mc, end - i2);
668 if (actual_mc > 0) {
669 pack_lhs(blockA, tri.getSubMapper(i2, IsLower ? k2 : k2 - kc), actual_kc, actual_mc);
670
671 gebp_kernel(other.getSubMapper(i2, 0), blockA, blockB, actual_mc, actual_kc, cols, Scalar(-1), -1, -1, 0,
672 0);
673 }
674 }
675 }
676 }
677 }
678}
679
680/* Optimized triangular solver with multiple left hand sides and the triangular matrix on the right
681 */
682template <typename Scalar, typename Index, int Mode, bool Conjugate, int TriStorageOrder, int OtherInnerStride>
683struct triangular_solve_matrix<Scalar, Index, OnTheRight, Mode, Conjugate, TriStorageOrder, ColMajor,
684 OtherInnerStride> {
685 static EIGEN_DONT_INLINE void run(Index size, Index otherSize, const Scalar* _tri, Index triStride, Scalar* _other,
686 Index otherIncr, Index otherStride, level3_blocking<Scalar, Scalar>& blocking);
687};
688
689template <typename Scalar, typename Index, int Mode, bool Conjugate, int TriStorageOrder, int OtherInnerStride>
690EIGEN_DONT_INLINE void triangular_solve_matrix<Scalar, Index, OnTheRight, Mode, Conjugate, TriStorageOrder, ColMajor,
691 OtherInnerStride>::run(Index size, Index otherSize, const Scalar* _tri,
692 Index triStride, Scalar* _other, Index otherIncr,
693 Index otherStride,
694 level3_blocking<Scalar, Scalar>& blocking) {
695 Index rows = otherSize;
696
697 std::ptrdiff_t l1, l2, l3;
698 manage_caching_sizes(GetAction, &l1, &l2, &l3);
699
700#if defined(EIGEN_VECTORIZE_AVX512) && defined(EIGEN_USE_AVX512_TRSM_R_KERNELS) && EIGEN_USE_AVX512_TRSM_R_KERNELS && \
701 EIGEN_ENABLE_AVX512_NOCOPY_TRSM_R_CUTOFFS
702 EIGEN_IF_CONSTEXPR ((OtherInnerStride == 1 &&
703 (std::is_same<Scalar, float>::value || std::is_same<Scalar, double>::value))) {
704 // TODO: Investigate better heuristics for cutoffs.
705 double L2Cap = 0.5; // 50% of L2 size
706 if (size < avx512_trsm_cutoff<Scalar>(l2, rows, L2Cap)) {
707 trsmKernelR<Scalar, Index, Mode, Conjugate, TriStorageOrder, OtherInnerStride, /*Specialized=*/true>::kernel(
708 size, rows, _tri, triStride, _other, 1, otherStride);
709 return;
710 }
711 }
712#endif
713
714 using LhsMapper = blas_data_mapper<Scalar, Index, ColMajor, Unaligned, OtherInnerStride>;
715 using RhsMapper = const_blas_data_mapper<Scalar, Index, TriStorageOrder>;
716 LhsMapper lhs(_other, otherStride, otherIncr);
717 RhsMapper rhs(_tri, triStride);
718
719 using Traits = gebp_traits<Scalar, Scalar>;
720 enum {
721 RhsStorageOrder = TriStorageOrder,
722 SmallPanelWidth = plain_enum_max(Traits::mr, Traits::nr),
723 IsLower = (Mode & Lower) == Lower
724 };
725
726 // Every k-block sweeps all rows of the left-hand side through gebp and packs its slab of a
727 // column-major triangle one column run at a time, so a large triangle alone deepens this kernel
728 // (issue #3162); a row-major triangle is packed by rows, as on the left.
729 const std::ptrdiff_t budget = triangular_solve_budget<Scalar>(l2, l3);
730 Index mc = (numext::mini)(rows, blocking.mc()); // cache block size along the M direction
731 // cache block size along the K direction
732 const Index kc = triangular_solve_kc<Scalar>(size, rows, (numext::maxi)(mc, size), budget,
733 /*slabRuns=*/TriStorageOrder == ColMajor, blocking);
734 // blockA packs kc x mc entries of the left-hand side, and rows can far exceed size. Past half the
735 // budget a deeper kc takes proportionally fewer rows per pass, so blockA does not outgrow the buffer
736 // the blocking chose.
737 if (kc > blocking.kc()) {
738 const std::ptrdiff_t maxA = (numext::maxi)(std::ptrdiff_t(blocking.kc()) * mc, budget / 2);
739 mc = (numext::mini)(mc, (numext::maxi)(Index(Traits::mr), numext::round_down(Index(maxA / kc), Index(Traits::mr))));
740 }
741
742 std::size_t sizeA = kc * mc;
743 std::size_t sizeB = kc * size;
744
745 ei_declare_aligned_stack_constructed_variable(Scalar, blockA, sizeA, blocking.blockA());
746 ei_declare_aligned_stack_constructed_variable(Scalar, blockB, sizeB, blocking.blockB());
747
748 gebp_kernel<Scalar, Scalar, Index, LhsMapper, Traits::mr, Traits::nr, false, Conjugate> gebp_kernel;
749 gemm_pack_rhs<Scalar, Index, RhsMapper, Traits::nr, RhsStorageOrder> pack_rhs;
750 gemm_pack_rhs<Scalar, Index, RhsMapper, Traits::nr, RhsStorageOrder, false, true> pack_rhs_panel;
751 gemm_pack_lhs<Scalar, Index, LhsMapper, Traits::mr, Traits::LhsProgress, typename Traits::LhsPacket4Packing, ColMajor,
752 false, true>
753 pack_lhs_panel;
754
755 for (Index k2 = IsLower ? size : 0; IsLower ? k2 > 0 : k2 < size; IsLower ? k2 -= kc : k2 += kc) {
756 const Index actual_kc = (numext::mini)(IsLower ? k2 : size - k2, kc);
757 Index actual_k2 = IsLower ? k2 - actual_kc : k2;
758
759 Index startPanel = IsLower ? 0 : k2 + actual_kc;
760 Index rs = IsLower ? actual_k2 : size - actual_k2 - actual_kc;
761 Scalar* geb = blockB + actual_kc * actual_kc;
762
763 if (rs > 0) pack_rhs(geb, rhs.getSubMapper(actual_k2, startPanel), actual_kc, rs);
764
765 // triangular packing (we only pack the panels off the diagonal,
766 // neglecting the blocks overlapping the diagonal
767 {
768 for (Index j2 = 0; j2 < actual_kc; j2 += SmallPanelWidth) {
769 Index actualPanelWidth = numext::mini<Index>(actual_kc - j2, SmallPanelWidth);
770 Index actual_j2 = actual_k2 + j2;
771 Index panelOffset = IsLower ? j2 + actualPanelWidth : 0;
772 Index panelLength = IsLower ? actual_kc - j2 - actualPanelWidth : j2;
773
774 if (panelLength > 0)
775 pack_rhs_panel(blockB + j2 * actual_kc, rhs.getSubMapper(actual_k2 + panelOffset, actual_j2), panelLength,
776 actualPanelWidth, actual_kc, panelOffset);
777 }
778 }
779
780 for (Index i2 = 0; i2 < rows; i2 += mc) {
781 const Index actual_mc = (numext::mini)(mc, rows - i2);
782
783 // triangular solver kernel
784 {
785 // for each small block of the diagonal (=> vertical panels of rhs)
786 for (Index j2 = IsLower ? (actual_kc - ((actual_kc % SmallPanelWidth) ? Index(actual_kc % SmallPanelWidth)
787 : Index(SmallPanelWidth)))
788 : 0;
789 IsLower ? j2 >= 0 : j2 < actual_kc; IsLower ? j2 -= SmallPanelWidth : j2 += SmallPanelWidth) {
790 Index actualPanelWidth = numext::mini<Index>(actual_kc - j2, SmallPanelWidth);
791 Index absolute_j2 = actual_k2 + j2;
792 Index panelOffset = IsLower ? j2 + actualPanelWidth : 0;
793 Index panelLength = IsLower ? actual_kc - j2 - actualPanelWidth : j2;
794
795 // GEBP
796 if (panelLength > 0) {
797 gebp_kernel(lhs.getSubMapper(i2, absolute_j2), blockA, blockB + j2 * actual_kc, actual_mc, panelLength,
798 actualPanelWidth, Scalar(-1), actual_kc, actual_kc, // strides
799 panelOffset, panelOffset); // offsets
800 }
801
802 {
803 // unblocked triangular solve
804 trsmKernelR<Scalar, Index, Mode, Conjugate, TriStorageOrder, OtherInnerStride,
805 /*Specialized=*/true>::kernel(actualPanelWidth, actual_mc,
806 _tri + absolute_j2 + absolute_j2 * triStride, triStride,
807 _other + i2 * otherIncr + absolute_j2 * otherStride, otherIncr,
808 otherStride);
809 }
810 // pack the just computed part of lhs to A
811 pack_lhs_panel(blockA, lhs.getSubMapper(i2, absolute_j2), actualPanelWidth, actual_mc, actual_kc, j2);
812 }
813 }
814
815 if (rs > 0)
816 gebp_kernel(lhs.getSubMapper(i2, startPanel), blockA, geb, actual_mc, actual_kc, rs, Scalar(-1), -1, -1, 0, 0);
817 }
818 }
819}
820} // end namespace internal
821
822} // end namespace Eigen
823
824#endif // EIGEN_TRIANGULAR_SOLVER_MATRIX_H
@ UnitDiag
Definition Constants.h:216
@ Lower
Definition Constants.h:212
@ Upper
Definition Constants.h:214
@ Unaligned
Definition Constants.h:236
@ ColMajor
Definition Constants.h:319
@ RowMajor
Definition Constants.h:321
@ OnTheLeft
Definition Constants.h:332
@ OnTheRight
Definition Constants.h:334