Eigen  5.0.1
 
Loading...
Searching...
No Matches
Jacobi.h
1// This file is part of Eigen, a lightweight C++ template library
2// for linear algebra.
3//
4// Copyright (C) 2009 Benoit Jacob <jacob.benoit.1@gmail.com>
5// Copyright (C) 2009 Gael Guennebaud <gael.guennebaud@inria.fr>
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_JACOBI_H
13#define EIGEN_JACOBI_H
14
15// IWYU pragma: private
16#include "./InternalHeaderCheck.h"
17
18namespace Eigen {
19
38template <typename Scalar>
40 public:
41 using RealScalar = typename NumTraits<Scalar>::Real;
42
44 EIGEN_DEVICE_FUNC JacobiRotation() {}
45
47 EIGEN_DEVICE_FUNC JacobiRotation(const Scalar& c, const Scalar& s) : m_c(c), m_s(s) {}
48
49 EIGEN_DEVICE_FUNC Scalar& c() { return m_c; }
50 EIGEN_DEVICE_FUNC Scalar c() const { return m_c; }
51 EIGEN_DEVICE_FUNC Scalar& s() { return m_s; }
52 EIGEN_DEVICE_FUNC Scalar s() const { return m_s; }
53
55 EIGEN_DEVICE_FUNC JacobiRotation operator*(const JacobiRotation& other) {
56 using numext::conj;
57 return JacobiRotation(m_c * other.m_c - conj(m_s) * other.m_s,
58 conj(m_c * conj(other.m_s) + conj(m_s) * conj(other.m_c)));
59 }
60
62 EIGEN_DEVICE_FUNC JacobiRotation transpose() const {
63 using numext::conj;
64 return JacobiRotation(m_c, -conj(m_s));
65 }
66
68 EIGEN_DEVICE_FUNC JacobiRotation adjoint() const {
69 using numext::conj;
70 return JacobiRotation(conj(m_c), -m_s);
71 }
72
73 template <typename Derived>
74 EIGEN_DEVICE_FUNC bool makeJacobi(const MatrixBase<Derived>&, Index p, Index q);
75 EIGEN_DEVICE_FUNC bool makeJacobi(const RealScalar& x, const Scalar& y, const RealScalar& z);
76
77 EIGEN_DEVICE_FUNC void makeGivens(const Scalar& p, const Scalar& q, Scalar* r = 0);
78
79 protected:
80 EIGEN_DEVICE_FUNC void makeGivens(const Scalar& p, const Scalar& q, Scalar* r, std::true_type);
81 EIGEN_DEVICE_FUNC void makeGivens(const Scalar& p, const Scalar& q, Scalar* r, std::false_type);
82 EIGEN_DEVICE_FUNC EIGEN_DONT_INLINE void makeGivensScaled(const Scalar& p, const Scalar& q, Scalar* r);
83
84 Scalar m_c, m_s;
85};
86
94template <typename Scalar>
95EIGEN_DEVICE_FUNC bool JacobiRotation<Scalar>::makeJacobi(const RealScalar& x, const Scalar& y, const RealScalar& z) {
96 using std::abs;
97 using std::sqrt;
98
99 const RealScalar abs_y = abs(y);
100 const RealScalar deno = RealScalar(2) * abs_y;
101 if (deno < (std::numeric_limits<RealScalar>::min)()) {
102 m_c = Scalar(1);
103 m_s = Scalar(0);
104 return false;
105 } else {
106 const RealScalar delta = x - z;
107 const RealScalar abs_delta = abs(delta);
108 // |t| = 1 / (|tau| + sqrt(1 + tau^2)), where tau = (x - z) / (2 * |y|).
109 // Scale this numerator/denominator pair by min(1, 1/|tau|), taking scale = 1 at tau = 0.
110 // Then numerator <= 1 and denominator <= 1 + sqrt(2); only a ratio <= 1 is squared.
111 // Normalize the pair directly to save the division forming |t|.
112 // Select operands before dividing: speculative evaluation of both reciprocal ratios can overflow.
113 const bool large_delta = abs_delta > deno;
114 const RealScalar ratio = (large_delta ? deno : abs_delta) / (large_delta ? abs_delta : deno);
115 const RealScalar numerator = large_delta ? ratio : RealScalar(1);
116 const RealScalar denominator = sqrt(RealScalar(1) + numext::abs2(ratio)) + (large_delta ? RealScalar(1) : ratio);
117 const RealScalar n = RealScalar(1) / sqrt(numext::abs2(numerator) + numext::abs2(denominator));
118 const RealScalar sine = numerator * n;
119 const RealScalar sign_t = delta > RealScalar(0) ? RealScalar(1) : RealScalar(-1);
120 EIGEN_IF_CONSTEXPR (NumTraits<Scalar>::IsComplex) {
121 m_s = -sign_t * (numext::conj(y) / abs_y) * sine;
122 } else {
123 // For real y, conj(y) / abs(y) is just its sign.
124 const RealScalar signed_sine = -sign_t * sine;
125 m_s = numext::real(y) < RealScalar(0) ? -signed_sine : signed_sine;
126 }
127 m_c = denominator * n;
128 return true;
129 }
130}
131
142template <typename Scalar>
143template <typename Derived>
144EIGEN_DEVICE_FUNC inline bool JacobiRotation<Scalar>::makeJacobi(const MatrixBase<Derived>& m, Index p, Index q) {
145 return makeJacobi(numext::real(m.coeff(p, p)), m.coeff(p, q), numext::real(m.coeff(q, q)));
146}
147
164template <typename Scalar>
165EIGEN_DEVICE_FUNC void JacobiRotation<Scalar>::makeGivens(const Scalar& p, const Scalar& q, Scalar* r) {
166 makeGivens(p, q, r, internal::bool_constant<NumTraits<Scalar>::IsComplex>());
167}
168
169// specialization for complexes
170template <typename Scalar>
171EIGEN_DEVICE_FUNC void JacobiRotation<Scalar>::makeGivens(const Scalar& p, const Scalar& q, Scalar* r, std::true_type) {
172 using numext::conj;
173 using std::abs;
174 using std::sqrt;
175
176 if (q == Scalar(0)) {
177 m_c = numext::real(p) < 0 ? Scalar(-1) : Scalar(1);
178 m_s = 0;
179 if (r) *r = m_c * p;
180 } else if (p == Scalar(0)) {
181 m_c = 0;
182 m_s = -q / abs(q);
183 if (r) *r = abs(q);
184 } else {
185 RealScalar p1 = numext::norm1(p);
186 RealScalar q1 = numext::norm1(q);
187 if (p1 >= q1) {
188 Scalar ps = p / p1;
189 RealScalar p2 = numext::abs2(ps);
190 Scalar qs = q / p1;
191 RealScalar q2 = numext::abs2(qs);
192
193 RealScalar u = sqrt(RealScalar(1) + q2 / p2);
194 if (numext::real(p) < RealScalar(0)) u = -u;
195
196 m_c = Scalar(1) / u;
197 m_s = -qs * conj(ps) * (m_c / p2);
198 if (r) *r = p * u;
199 } else {
200 Scalar ps = p / q1;
201 RealScalar p2 = numext::abs2(ps);
202 Scalar qs = q / q1;
203 RealScalar q2 = numext::abs2(qs);
204
205 RealScalar u = q1 * sqrt(p2 + q2);
206 if (numext::real(p) < RealScalar(0)) u = -u;
207
208 p1 = abs(p);
209 ps = p / p1;
210 m_c = p1 / u;
211 m_s = -conj(ps) * (q / u);
212 if (r) *r = ps * u;
213 }
214 }
215}
216
217// specialization for reals
218template <typename Scalar>
219EIGEN_DEVICE_FUNC void JacobiRotation<Scalar>::makeGivens(const Scalar& p, const Scalar& q, Scalar* r,
220 std::false_type) {
221 using std::abs;
222 using std::sqrt;
223 if (numext::is_exactly_zero_no_flush(q)) {
224 // DAZ can hide the sign; MSVC's non-intrinsic abs(float) can flush the
225 // result through a float->double->float conversion even with FTZ alone.
226 if (abs(p) < (std::numeric_limits<Scalar>::min)() && !numext::is_exactly_zero_no_flush(p)) {
227 makeGivensScaled(p, q, r);
228 return;
229 }
230 m_c = p < Scalar(0) ? Scalar(-1) : Scalar(1);
231 m_s = Scalar(0);
232 if (r) *r = abs(p);
233 return;
234 }
235 if (numext::is_exactly_zero_no_flush(p)) {
236 if (abs(q) < (std::numeric_limits<Scalar>::min)()) {
237 makeGivensScaled(p, q, r);
238 return;
239 }
240 m_c = Scalar(0);
241 m_s = q < Scalar(0) ? Scalar(1) : Scalar(-1);
242 if (r) *r = abs(q);
243 return;
244 }
245
246 // Safe-range thresholds following Anderson, "Algorithm 978: Safe Scaling
247 // in the Level 1 BLAS", ACM TOMS 44(1), 2017. When both |p| and |q| lie
248 // in (rtmin, rtmax), the direct formula r = p * sqrt(1 + (q/p)^2) cannot
249 // over- or underflow before the true result would. Outside that range
250 // we prescale with safe_scaling (normal powers of two for supported binary
251 // scalars), preserving significant float/double subnormal inputs under FTZ/DAZ. This preserves the
252 // existing Eigen sign convention (r >= 0, sign carried in c).
253 const Scalar safmin = (std::numeric_limits<Scalar>::min)();
254 const Scalar safmax = Scalar(1) / safmin;
255 const Scalar rtmin = sqrt(safmin);
256 const Scalar rtmax = sqrt(safmax / Scalar(2));
257 const Scalar abs_p = abs(p);
258 const Scalar abs_q = abs(q);
259 const Scalar mx = numext::maxi(abs_p, abs_q);
260 const Scalar mn = numext::mini(abs_p, abs_q);
261
262 if (EIGEN_PREDICT_TRUE(mx < rtmax && mn > rtmin)) {
263 // Safe range: existing direct formulas are stable.
264 if (abs_p > abs_q) {
265 Scalar t = q / p;
266 Scalar u = sqrt(Scalar(1) + numext::abs2(t));
267 if (p < Scalar(0)) u = -u;
268 m_c = Scalar(1) / u;
269 m_s = -t * m_c;
270 if (r) *r = p * u;
271 } else {
272 Scalar t = p / q;
273 Scalar u = sqrt(Scalar(1) + numext::abs2(t));
274 if (q < Scalar(0)) u = -u;
275 m_s = -Scalar(1) / u;
276 m_c = -t * m_s;
277 if (r) *r = q * u;
278 }
279 } else {
280 makeGivensScaled(p, q, r);
281 }
282}
283
284template <typename Scalar>
285EIGEN_DEVICE_FUNC EIGEN_DONT_INLINE void JacobiRotation<Scalar>::makeGivensScaled(const Scalar& p, const Scalar& q,
286 Scalar* r) {
287 using Scaling = internal::safe_scaling<Scalar>;
289 scaled << p, q;
290 const Scalar maximum = Scaling::recover_flushed_max_coeff(scaled, scaled.cwiseAbs().maxCoeff());
291 const auto factors = Scaling::scale_to(scaled, scaled, maximum);
292 const Scalar ps = scaled.x(), qs = scaled.y();
293 Scalar norm;
294 if (numext::abs(ps) > numext::abs(qs)) {
295 const Scalar t = qs / ps;
296 Scalar u = numext::sqrt<Scalar>(Scalar(1) + numext::abs2(t));
297 if (ps < Scalar(0)) u = -u;
298 m_c = Scalar(1) / u;
299 m_s = -t * m_c;
300 if (r) norm = ps * u;
301 } else {
302 const Scalar t = ps / qs;
303 Scalar u = numext::sqrt<Scalar>(Scalar(1) + numext::abs2(t));
304 if (qs < Scalar(0)) u = -u;
305 m_s = -Scalar(1) / u;
306 m_c = -t * m_s;
307 if (r) norm = qs * u;
308 }
309 if (r) {
310 // Restore subnormal r through the same FTZ/DAZ-safe path as the inputs.
312 result[0] = norm;
313 Scaling::unscale_in_place(result, maximum, factors);
314 *r = result[0];
315 }
316}
317
318/****************************************************************************************
319 * Implementation of MatrixBase methods
320 ****************************************************************************************/
321
322namespace internal {
330template <typename VectorX, typename VectorY, typename OtherScalar>
331EIGEN_DEVICE_FUNC void apply_rotation_in_the_plane(DenseBase<VectorX>& xpr_x, DenseBase<VectorY>& xpr_y,
332 const JacobiRotation<OtherScalar>& j);
333} // namespace internal
334
341template <typename Derived>
342template <typename OtherScalar>
343EIGEN_DEVICE_FUNC inline void MatrixBase<Derived>::applyOnTheLeft(Index p, Index q,
345 RowXpr x(this->row(p));
346 RowXpr y(this->row(q));
347 internal::apply_rotation_in_the_plane(x, y, j);
348}
349
356template <typename Derived>
357template <typename OtherScalar>
358EIGEN_DEVICE_FUNC inline void MatrixBase<Derived>::applyOnTheRight(Index p, Index q,
360 ColXpr x(this->col(p));
361 ColXpr y(this->col(q));
362 internal::apply_rotation_in_the_plane(x, y, j.transpose());
363}
364
365namespace internal {
366
367template <typename Scalar, typename OtherScalar, int SizeAtCompileTime, int MinAlignment, bool Vectorizable>
368struct apply_rotation_in_the_plane_selector {
369 static EIGEN_DEVICE_FUNC inline void run(Scalar* x, Index incrx, Scalar* y, Index incry, Index size, OtherScalar c,
370 OtherScalar s) {
371 for (Index i = 0; i < size; ++i) {
372 Scalar xi = *x;
373 Scalar yi = *y;
374 *x = c * xi + numext::conj(s) * yi;
375 *y = -s * xi + numext::conj(c) * yi;
376 x += incrx;
377 y += incry;
378 }
379 }
380};
381
382template <typename Scalar, typename OtherScalar, int SizeAtCompileTime, int MinAlignment>
383struct apply_rotation_in_the_plane_selector<Scalar, OtherScalar, SizeAtCompileTime, MinAlignment,
384 true /* vectorizable */> {
385 EIGEN_DEVICE_FUNC static void run(Scalar* x, Index incrx, Scalar* y, Index incry, Index size, OtherScalar c,
386 OtherScalar s) {
387 using Packet = typename packet_traits<Scalar>::type;
388 using OtherPacket = typename packet_traits<OtherScalar>::type;
389
390 constexpr int RequiredAlignment =
391 (std::max<int>)(unpacket_traits<Packet>::alignment, unpacket_traits<OtherPacket>::alignment);
392 constexpr Index PacketSize = packet_traits<Scalar>::size;
393
394 EIGEN_IF_CONSTEXPR (SizeAtCompileTime == Dynamic) {
395 /*** dynamic-size vectorized paths ***/
396 if (size >= 2 * PacketSize && ((incrx == 1 && incry == 1) || PacketSize == 1)) {
397 // both vectors are sequentially stored in memory => vectorization
398 constexpr Index Peeling = 2;
399
400 Index alignedStart = internal::first_default_aligned(y, size);
401 Index alignedEnd = alignedStart + ((size - alignedStart) / PacketSize) * PacketSize;
402
403 const OtherPacket pc = pset1<OtherPacket>(c);
404 const OtherPacket ps = pset1<OtherPacket>(s);
405 conj_helper<OtherPacket, Packet, NumTraits<OtherScalar>::IsComplex, false> pcj;
406 conj_helper<OtherPacket, Packet, false, false> pm;
407
408 for (Index i = 0; i < alignedStart; ++i) {
409 Scalar xi = x[i];
410 Scalar yi = y[i];
411 x[i] = c * xi + numext::conj(s) * yi;
412 y[i] = -s * xi + numext::conj(c) * yi;
413 }
414
415 Scalar* EIGEN_RESTRICT px = x + alignedStart;
416 Scalar* EIGEN_RESTRICT py = y + alignedStart;
417
418 if (internal::first_default_aligned(x, size) == alignedStart) {
419 for (Index i = alignedStart; i < alignedEnd; i += PacketSize) {
420 Packet xi = pload<Packet>(px);
421 Packet yi = pload<Packet>(py);
422 pstore(px, pm.pmadd(pc, xi, pcj.pmul(ps, yi)));
423 pstore(py, pcj.pmsub(pc, yi, pm.pmul(ps, xi)));
424 px += PacketSize;
425 py += PacketSize;
426 }
427 } else {
428 Index peelingEnd = alignedStart + ((size - alignedStart) / (Peeling * PacketSize)) * (Peeling * PacketSize);
429 for (Index i = alignedStart; i < peelingEnd; i += Peeling * PacketSize) {
430 Packet xi = ploadu<Packet>(px);
431 Packet xi1 = ploadu<Packet>(px + PacketSize);
432 Packet yi = pload<Packet>(py);
433 Packet yi1 = pload<Packet>(py + PacketSize);
434 pstoreu(px, pm.pmadd(pc, xi, pcj.pmul(ps, yi)));
435 pstoreu(px + PacketSize, pm.pmadd(pc, xi1, pcj.pmul(ps, yi1)));
436 pstore(py, pcj.pmsub(pc, yi, pm.pmul(ps, xi)));
437 pstore(py + PacketSize, pcj.pmsub(pc, yi1, pm.pmul(ps, xi1)));
438 px += Peeling * PacketSize;
439 py += Peeling * PacketSize;
440 }
441 if (alignedEnd != peelingEnd) {
442 Packet xi = ploadu<Packet>(x + peelingEnd);
443 Packet yi = pload<Packet>(y + peelingEnd);
444 pstoreu(x + peelingEnd, pm.pmadd(pc, xi, pcj.pmul(ps, yi)));
445 pstore(y + peelingEnd, pcj.pmsub(pc, yi, pm.pmul(ps, xi)));
446 }
447 }
448
449 for (Index i = alignedEnd; i < size; ++i) {
450 Scalar xi = x[i];
451 Scalar yi = y[i];
452 x[i] = c * xi + numext::conj(s) * yi;
453 y[i] = -s * xi + numext::conj(c) * yi;
454 }
455 return;
456 }
457 }
458
459 EIGEN_IF_CONSTEXPR (SizeAtCompileTime != Dynamic && MinAlignment >= RequiredAlignment) {
460 /*** fixed-size vectorized path ***/
461 const OtherPacket pc = pset1<OtherPacket>(c);
462 const OtherPacket ps = pset1<OtherPacket>(s);
463 conj_helper<OtherPacket, Packet, NumTraits<OtherScalar>::IsComplex, false> pcj;
464 conj_helper<OtherPacket, Packet, false, false> pm;
465 Scalar* EIGEN_RESTRICT px = x;
466 Scalar* EIGEN_RESTRICT py = y;
467 for (Index i = 0; i < size; i += PacketSize) {
468 Packet xi = pload<Packet>(px);
469 Packet yi = pload<Packet>(py);
470 pstore(px, pm.pmadd(pc, xi, pcj.pmul(ps, yi)));
471 pstore(py, pcj.pmsub(pc, yi, pm.pmul(ps, xi)));
472 px += PacketSize;
473 py += PacketSize;
474 }
475 return;
476 }
477
478 /*** non-vectorized path ***/
479 apply_rotation_in_the_plane_selector<Scalar, OtherScalar, SizeAtCompileTime, MinAlignment, false>::run(
480 x, incrx, y, incry, size, c, s);
481 }
482};
483
484template <typename VectorX, typename VectorY, typename OtherScalar>
485EIGEN_DEVICE_FUNC void inline apply_rotation_in_the_plane(DenseBase<VectorX>& xpr_x, DenseBase<VectorY>& xpr_y,
486 const JacobiRotation<OtherScalar>& j) {
487 using Scalar = typename VectorX::Scalar;
488 constexpr bool Vectorizable = (int(evaluator<VectorX>::Flags) & int(evaluator<VectorY>::Flags) & PacketAccessBit) &&
489 (int(packet_traits<Scalar>::size) == int(packet_traits<OtherScalar>::size));
490
491 eigen_assert(xpr_x.size() == xpr_y.size());
492 Index size = xpr_x.size();
493 Index incrx = xpr_x.derived().innerStride();
494 Index incry = xpr_y.derived().innerStride();
495
496 Scalar* EIGEN_RESTRICT x = &xpr_x.derived().coeffRef(0);
497 Scalar* EIGEN_RESTRICT y = &xpr_y.derived().coeffRef(0);
498
499 OtherScalar c = j.c();
500 OtherScalar s = j.s();
501 if (numext::is_exactly_one(c) && numext::is_exactly_zero(s)) return;
502
503 constexpr int Alignment = (std::min)(int(evaluator<VectorX>::Alignment), int(evaluator<VectorY>::Alignment));
504 apply_rotation_in_the_plane_selector<Scalar, OtherScalar, VectorX::SizeAtCompileTime, Alignment, Vectorizable>::run(
505 x, incrx, y, incry, size, c, s);
506}
507
508template <typename MatrixType, typename RealScalar, typename Index>
509EIGEN_DONT_INLINE void real_2x2_jacobi_svd(const MatrixType& matrix, Index p, Index q,
510 JacobiRotation<RealScalar>* j_left, JacobiRotation<RealScalar>* j_right) {
511 // Extract 2x2 submatrix into scalars (avoids Matrix construction on stack).
512 const RealScalar m00 = numext::real(matrix.coeff(p, p));
513 const RealScalar m01 = numext::real(matrix.coeff(p, q));
514 const RealScalar m10 = numext::real(matrix.coeff(q, p));
515 const RealScalar m11 = numext::real(matrix.coeff(q, q));
516
517 // Compute the symmetrizing rotation rot1 such that rot1 * [m] is symmetric.
518 const RealScalar t = m00 + m11;
519 const RealScalar d = m10 - m01;
520
521 RealScalar c1, s1;
522 if (numext::abs(d) < (std::numeric_limits<RealScalar>::min)()) {
523 c1 = RealScalar(1);
524 s1 = RealScalar(0);
525 } else {
526 // If d!=0, then t/d cannot overflow because the magnitude of the
527 // entries forming d are not too small compared to the ones forming t.
528 RealScalar u = t / d;
529 s1 = RealScalar(1) / numext::sqrt<RealScalar>(RealScalar(1) + numext::abs2(u));
530 c1 = u * s1;
531 }
532
533 // Apply rot1 to the 2x2 submatrix inline (avoids rotation dispatch overhead).
534 // Result is symmetric, so we only need 3 values: a00, a01 (== a10), a11.
535 const RealScalar a00 = c1 * m00 + s1 * m10;
536 const RealScalar a01 = c1 * m01 + s1 * m11;
537 const RealScalar a11 = -s1 * m01 + c1 * m11;
538
539 // Compute the diagonalizing rotation j_right from the symmetrized matrix.
540 j_right->makeJacobi(a00, a01, a11);
541
542 // Compose j_left = rot1 * j_right^T inline (avoids template machinery overhead).
543 const RealScalar jr_c = j_right->c();
544 const RealScalar jr_s = j_right->s();
545 j_left->c() = c1 * jr_c + s1 * jr_s;
546 j_left->s() = s1 * jr_c - c1 * jr_s;
547}
548
549} // end namespace internal
550
551} // end namespace Eigen
552
553#endif // EIGEN_JACOBI_H
constexpr ColXpr col(Index i)
Definition DenseBase.h:1081
constexpr RowXpr row(Index i)
Definition DenseBase.h:1094
typename internal::traits< Derived >::Scalar Scalar
Definition DenseBase.h:63
Rotation given by a cosine-sine pair.
Definition Jacobi.h:39
JacobiRotation()
Definition Jacobi.h:44
JacobiRotation(const Scalar &c, const Scalar &s)
Definition Jacobi.h:47
bool makeJacobi(const MatrixBase< Derived > &, Index p, Index q)
Definition Jacobi.h:144
JacobiRotation adjoint() const
Definition Jacobi.h:68
JacobiRotation transpose() const
Definition Jacobi.h:62
JacobiRotation operator*(const JacobiRotation &other)
Definition Jacobi.h:55
void makeGivens(const Scalar &p, const Scalar &q, Scalar *r=0)
Definition Jacobi.h:165
Base class for all dense matrices, vectors, and expressions.
Definition MatrixBase.h:53
void applyOnTheLeft(const EigenBase< OtherDerived > &other)
Definition MatrixBase.h:553
void applyOnTheRight(const EigenBase< OtherDerived > &other)
Definition MatrixBase.h:542
The matrix class, also used for vectors and row-vectors.
Definition Matrix.h:188
constexpr unsigned int PacketAccessBit
Definition Constants.h:98