Fix four defects found by auditing Doxygen claims against the code
These four defects came out of an audit that checked Doxygen claims against the implementations they describe. In each case the documentation was right and the code was wrong. Every fix ships with a regression test that fails at the parent commit.
Core: isLowerTriangular() skipped one coefficient per column
The strictly-upper row bound was numext::mini(j, rows() - 1), but rows [0, min(j, rows())) of column j lie above the diagonal. For a wide matrix the last coefficient of every column past the diagonal block was never inspected:
MatrixXd A(2, 3);
A << 1, 0, 0,
0, 1, 100; // A(1,2) is strictly above the diagonal
A.isLowerTriangular(); // returned trueisUpperTriangular() has a differently shaped loop and was always correct, so it serves as the mirror control in the test.
Core: ArrayBase::shiftRight() / shiftLeft() did not compile, and disagreed with themselves once they did
The scalar operator() of scalar_shift_right_op / scalar_shift_left_op called numext::arithmetic_shift_right(a) and numext::logical_shift_left(a), omitting the shift count; the helpers take (a, n). The scalar path is always instantiated, so both public methods were unusable in every configuration. No test in test/ or unsupported/test/ referenced either name, which is how this shipped. The \sa targets named MatrixBase::shift_right()/shift_left(); neither exists, and the methods live on ArrayBase.
Making them compile exposed a second defect, raised in review by @florian360 and @onalante-ebay. numext::arithmetic_shift_right bit-cast through SignedScalar for every integral type, so an unsigned Scalar had its high bit — an ordinary value bit, not a sign — propagated. Every backend that defines an unsigned parithmetic_shift_right implements it as a logical shift (SSE _mm_srli_epi32, NEON vshrq_n_u32, LSX __lsx_vsrli_*, AVX via plogical_shift_right) and none sign-extends, so the two evaluation paths of a single expression returned different values:
ArrayX<uint32_t> a = ArrayX<uint32_t>::Constant(64, 0x80000000u);
a.shiftRight<1>(); // 0x40000000 when assigned, 0xc0000000 read coefficient-wiseThe shift now goes through the scalar's own signedness, which moves the scalar path onto the semantics every backend already had. No library code calls the helper with an unsigned type — every internal parithmetic_shift_right use is on a signed exponent packet — so the only reachable effect is on the public shiftRight(), whose Doxygen block now states the fill bit.
Geometry: Transform::inverse(Projective) returned uninitialized memory
For Mode != Projective the primary template of internal::projective_transform_inverse had an empty run() body, so res came back default-constructed with no assert:
Affine3d T = ...;
T.inverse(Projective); // uninitialized#Projective is documented as the hint that assumes no structure, so this was the failure mode of following the documentation. Since every non-Projective mode is affine by type, the hint is now honoured with the general affine inverse — which makes no orthonormality assumption and is representable for AffineCompact, whose non-square stored matrix is why the empty primary template existed.
IterativeLinearSolvers: DGMRES::iterations() returned the iteration cap
dgmres() used m_iterations only as the loop bound and never wrote the count back, so iterations() returned maxIterations() after every solve, converged ones included.
Testing
shift_test in array_cwise now drives shiftLeft<N>()/shiftRight<N>() directly instead of open-coding their functors, references Scalar's own operator>> so the arithmetic-right arm is independent of the implementation under test, and runs on uint32_t, uint64_t, int8_t and uint8_t in addition to int and Index.
array_cwise (40 splits), numext, packetmath, triangular, geo_transformations and dgmres all pass — 88 tests, aarch64/NEON, Release.
Reverting each fix individually breaks its test: 2 triangular, 2 dgmres, 5 geo_transformations, 21 compile errors for the shift-count fix, and numext plus array_cwise splits 38/39/40 for the unsigned shift semantics. x86 and other-ISA behaviour is left to CI; the packet paths themselves are unchanged.