Files
2026-07-13 12:47:05 +08:00

369 lines
14 KiB
C++

/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author Yurii Shyrma (iuriish@yahoo.com)
//
#include <helpers/EigenValsAndVecs.h>
#include <helpers/HessenbergAndSchur.h>
namespace sd {
namespace ops {
namespace helpers {
//////////////////////////////////////////////////////////////////////////
template <typename T>
EigenValsAndVecs<T>::EigenValsAndVecs(NDArray& matrix)
: _Vals(matrix.dataType(), matrix.getContext(), true),
_Vecs(matrix.dataType(), matrix.getContext(), true) {
if (matrix.rankOf() != 2)
THROW_EXCEPTION("ops::helpers::EigenValsAndVecs constructor: input matrix must be 2D !");
if (matrix.sizeAt(0) != matrix.sizeAt(1))
THROW_EXCEPTION("ops::helpers::EigenValsAndVecs constructor: input array must be 2D square matrix !");
Schur<T> schur(matrix);
NDArray* schurMatrixU = schur.u;
NDArray* schurMatrixT = schur.t;
std::vector<LongType> shape = {schurMatrixU->sizeAt(1), schurMatrixU->sizeAt(1), 2};
_Vecs = NDArray(matrix.ordering(), shape, matrix.dataType(),
matrix.getContext());
std::vector<LongType> shape2 = {matrix.sizeAt(1), 2};
_Vals = NDArray(matrix.ordering(), shape2, matrix.dataType(), matrix.getContext());
// sequence of methods calls matters
calcEigenVals(*schurMatrixT);
calcPseudoEigenVecs(*schurMatrixT, *schurMatrixU); // pseudo-eigenvectors are real and will be stored in schurMatrixU
calcEigenVecs(*schurMatrixU);
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void calcEigenVals_(NDArray& schurMatrixT, NDArray& _Vals) {
const int numOfCols = schurMatrixT.sizeAt(1);
// calculate eigenvalues _Vals
int i = 0;
while (i < numOfCols) {
if (i == numOfCols - 1 || schurMatrixT.t<T>(i + 1, i) == T(0.f)) {
_Vals.r<T>(i, 0) = schurMatrixT.t<T>(i, i); // real part
_Vals.r<T>(i, 1) = T(0); // imaginary part
if (!math::sd_isfin<T>(_Vals.t<T>(static_cast<T>(i), static_cast<T>(0)))) {
THROW_EXCEPTION("ops::helpers::igenValsAndVec::calcEigenVals: got infinite eigen value !");
return;
}
++i;
} else {
T p = T(0.5) * (schurMatrixT.t<T>(i, i) - schurMatrixT.t<T>(i + 1, i + 1));
T z;
{
T t0 = schurMatrixT.t<T>(i + 1, i);
T t1 = schurMatrixT.t<T>(i, i + 1);
T maxval = math::sd_max<T>(math::sd_abs<T,T>(p), math::sd_max<T>(math::sd_abs<T,T>(t0), math::sd_abs<T,T>(t1)));
t0 /= maxval;
t1 /= maxval;
T p0 = p / maxval;
z = maxval * math::sd_sqrt<T, T>(math::sd_abs<T,T>(p0 * p0 + t0 * t1));
}
_Vals.r<T>(i, 0) = _Vals.r<T>(i + 1, 0) = schurMatrixT.t<T>(i + 1, i + 1) + p;
_Vals.r<T>(i, 1) = z;
_Vals.r<T>(i + 1, 1) = -z;
if (!(math::sd_isfin<T>(_Vals.t<T>(i, 0)) && math::sd_isfin<T>(_Vals.t<T>(i + 1, 0)) &&
math::sd_isfin<T>(_Vals.t<T>(i, 1))) &&
math::sd_isfin<T>(_Vals.t<T>(i + 1, 1))) {
THROW_EXCEPTION("ops::helpers::igenValsAndVec::calcEigenVals: got infinite eigen value !");
return;
}
i += 2;
}
}
}
template <typename T>
void EigenValsAndVecs<T>::calcEigenVals(NDArray& schurMatrixT) {
calcEigenVals_<T>(schurMatrixT, _Vals);
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void calcPseudoEigenVecs_(NDArray& schurMatrixT, NDArray& schurMatrixU, NDArray& _Vals) {
const int numOfCols = schurMatrixU.sizeAt(1);
T norm = static_cast<T>(0);
for (int j = 0; j < numOfCols; ++j) {
NDArray *viewPtr = schurMatrixT({j, j + 1, math::sd_max<LongType>(j - 1, 0), numOfCols});
auto* reduceResult = viewPtr->reduceNumber(reduce::ASum);
norm += reduceResult->template t<T>(0);
delete reduceResult;
delete viewPtr;
}
if (norm == T(0)) return;
for (int n = numOfCols - 1; n >= 0; n--) {
T p = _Vals.t<T>(n, 0); // real part
T q = _Vals.t<T>(n, 1); // imaginary part
if (q == (T)0) { // not complex
T lastr((T)0), lastw((T)0);
int l = n;
schurMatrixT.r<T>(n, n) = T(1);
for (int i = n - 1; i >= 0; i--) {
T w = schurMatrixT.t<T>(i, i) - p;
NDArray *view1Ptr = schurMatrixT({i, i + 1, l, n + 1}, true);
NDArray *view2Ptr = schurMatrixT({l, n + 1, n, n + 1}, true);
NDArray *dotResult = mmul(*view1Ptr, *view2Ptr);
T r = dotResult->template t<T>(0);
delete view1Ptr;
delete view2Ptr;
delete dotResult;
if (_Vals.t<T>(i, 1) < T(0)) {
lastw = w;
lastr = r;
} else {
l = i;
if (_Vals.t<T>(i, 1) == T(0)) {
if (w != T(0))
schurMatrixT.r<T>(i, n) = -r / w;
else
schurMatrixT.r<T>(i, n) = -r / (DataTypeUtils::eps<T>() * norm);
} else {
T x = schurMatrixT.t<T>(i, i + 1);
T y = schurMatrixT.t<T>(i + 1, i);
T denom = (_Vals.t<T>(i, 0) - p) * (_Vals.t<T>(i, 0) - p) + _Vals.t<T>(i, 1) * _Vals.t<T>(i, 1);
T t = (x * lastr - lastw * r) / denom;
schurMatrixT.r<T>(i, n) = t;
if (math::sd_abs<T,T>(x) > math::sd_abs<T,T>(lastw))
schurMatrixT.r<T>(i + 1, n) = (-r - w * t) / x;
else
schurMatrixT.r<T>(i + 1, n) = (-lastr - y * t) / lastw;
}
T t = math::sd_abs<T,T>(schurMatrixT.t<T>(i, n));
if ((DataTypeUtils::eps<T>() * t) * t > T(1)) {
NDArray *divViewPtr = schurMatrixT({schurMatrixT.sizeAt(0) - numOfCols + i, -1, n, n + 1});
*divViewPtr /= t;
delete divViewPtr;
}
}
}
} else if (q < T(0) && n > 0) { // complex
T lastra(0), lastsa(0), lastw(0);
int l = n - 1;
if (math::sd_abs<T,T>(schurMatrixT.t<T>(n, n - 1)) > math::sd_abs<T,T>(schurMatrixT.t<T>(n - 1, n))) {
schurMatrixT.r<T>(n - 1, n - 1) = q / schurMatrixT.t<T>(n, n - 1);
schurMatrixT.r<T>(n - 1, n) = -(schurMatrixT.t<T>(n, n) - p) / schurMatrixT.t<T>(n, n - 1);
} else {
EigenValsAndVecs<T>::divideComplexNums(T(0), -schurMatrixT.t<T>(n - 1, n), schurMatrixT.t<T>(n - 1, n - 1) - p,
q, schurMatrixT.r<T>(n - 1, n - 1), schurMatrixT.r<T>(n - 1, n));
}
schurMatrixT.r<T>(n, n - 1) = T(0);
schurMatrixT.r<T>(n, n) = T(1);
for (int i = n - 2; i >= 0; i--) {
NDArray *raView1Ptr = schurMatrixT({i, i + 1, l, n + 1}, true);
NDArray *raView2Ptr = schurMatrixT({l, n + 1, n - 1, n}, true);
NDArray *raDotResult = mmul(*raView1Ptr, *raView2Ptr);
T ra = raDotResult->template t<T>(0);
delete raView1Ptr;
delete raView2Ptr;
delete raDotResult;
NDArray *saView1Ptr = schurMatrixT({i, i + 1, l, n + 1}, true);
NDArray *saView2Ptr = schurMatrixT({l, n + 1, n, n + 1}, true);
NDArray *saDotResult = mmul(*saView1Ptr, *saView2Ptr);
T sa = saDotResult->template t<T>(0);
delete saView1Ptr;
delete saView2Ptr;
delete saDotResult;
T w = schurMatrixT.t<T>(i, i) - p;
if (_Vals.t<T>(i, 1) < T(0)) {
lastw = w;
lastra = ra;
lastsa = sa;
} else {
l = i;
if (_Vals.t<T>(i, 1) == T(0)) {
EigenValsAndVecs<T>::divideComplexNums(-ra, -sa, w, q, schurMatrixT.r<T>(i, n - 1),
schurMatrixT.r<T>(i, n));
} else {
T x = schurMatrixT.t<T>(i, i + 1);
T y = schurMatrixT.t<T>(i + 1, i);
T vr = (_Vals.t<T>(i, 0) - p) * (_Vals.t<T>(i, 0) - p) + _Vals.t<T>(i, 1) * _Vals.t<T>(i, 1) - q * q;
T vi = (_Vals.t<T>(i, 0) - p) * T(2) * q;
if ((vr == T(0)) && (vi == T(0)))
vr = DataTypeUtils::eps<T>() * norm *
(math::sd_abs<T,T>(w) + math::sd_abs<T,T>(q) + math::sd_abs<T,T>(x) + math::sd_abs<T,T>(y) +
math::sd_abs<T,T>(lastw));
EigenValsAndVecs<T>::divideComplexNums(x * lastra - lastw * ra + q * sa, x * lastsa - lastw * sa - q * ra,
vr, vi, schurMatrixT.r<T>(i, n - 1), schurMatrixT.r<T>(i, n));
if (math::sd_abs<T,T>(x) > (math::sd_abs<T,T>(lastw) + math::sd_abs<T,T>(q))) {
schurMatrixT.r<T>(i + 1, n - 1) =
(-ra - w * schurMatrixT.t<T>(i, n - 1) + q * schurMatrixT.t<T>(i, n)) / x;
schurMatrixT.r<T>(i + 1, n) = (-sa - w * schurMatrixT.t<T>(i, n) - q * schurMatrixT.t<T>(i, n - 1)) / x;
} else
EigenValsAndVecs<T>::divideComplexNums(-lastra - y * schurMatrixT.t<T>(i, n - 1),
-lastsa - y * schurMatrixT.t<T>(i, n), lastw, q,
schurMatrixT.r<T>(i + 1, n - 1), schurMatrixT.r<T>(i + 1, n));
}
T t = math::sd_max<T>(math::sd_abs<T,T>(schurMatrixT.t<T>(i, n - 1)), math::sd_abs<T,T>(schurMatrixT.t<T>(i, n)));
if ((DataTypeUtils::eps<T>() * t) * t > T(1)) {
NDArray *divViewPtr = schurMatrixT({i, numOfCols, n - 1, n + 1});
*divViewPtr /= t;
delete divViewPtr;
}
}
}
n--;
} else
THROW_EXCEPTION("ops::helpers::EigenValsAndVecs::calcEigenVecs: internal bug !");
}
for (int j = numOfCols - 1; j >= 0; j--) {
NDArray *uViewPtr = schurMatrixU({0, 0, 0, j + 1}, true);
NDArray *tViewPtr = schurMatrixT({0, j + 1, j, j + 1}, true);
NDArray *assignResult = mmul(*uViewPtr, *tViewPtr);
delete uViewPtr;
delete tViewPtr;
NDArray *uAssignPtr = schurMatrixU({0, 0, j, j + 1}, true);
uAssignPtr->assign(assignResult);
delete uAssignPtr;
delete assignResult;
}
}
template <typename T>
void EigenValsAndVecs<T>::calcPseudoEigenVecs(NDArray& schurMatrixT, NDArray& schurMatrixU) {
calcPseudoEigenVecs_<T>(schurMatrixT, schurMatrixU, _Vals);
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void calcEigenVecs_(NDArray& schurMatrixU, NDArray& _Vals, NDArray& _Vecs) {
const T precision = T(2) * DataTypeUtils::eps<T>();
const int numOfCols = schurMatrixU.sizeAt(1);
for (int j = 0; j < numOfCols; ++j) {
if (math::sd_abs<T,T>(_Vals.t<T>(j, 1)) <= math::sd_abs<T,T>(_Vals.t<T>(j, 0)) * precision ||
j + 1 == numOfCols) { // real
_Vecs.syncToDevice();
NDArray *assignPtr = schurMatrixU({0, 0, j, j + 1});
NDArray *vecsViewPtr = _Vecs({0, 0, j, j + 1, 0, 1});
vecsViewPtr->assign(assignPtr);
delete assignPtr;
delete vecsViewPtr;
NDArray *vecsView2Ptr = _Vecs({0, 0, j, j + 1, 1, 2});
*vecsView2Ptr = (T)0;
delete vecsView2Ptr;
// normalize
NDArray *norm2ViewPtr = _Vecs({0, 0, j, j + 1, 0, 1});
auto* norm2Result = norm2ViewPtr->reduceNumber(reduce::SquaredNorm);
const T norm2 = norm2Result->template t<T>(0);
delete norm2Result;
if (norm2 > (T)0) *norm2ViewPtr /= math::sd_sqrt<T, T>(norm2);
delete norm2ViewPtr;
} else { // complex
for (int i = 0; i < numOfCols; ++i) {
_Vecs.r<T>(i, j, 0) = _Vecs.r<T>(i, j + 1, 0) = schurMatrixU.t<T>(i, j);
_Vecs.r<T>(i, j, 1) = schurMatrixU.t<T>(i, j + 1);
_Vecs.r<T>(i, j + 1, 1) = -schurMatrixU.t<T>(i, j + 1);
}
// normalize
NDArray *norm2View1Ptr = _Vecs({0, 0, j, j + 1, 0, 0});
auto* norm2Result1 = norm2View1Ptr->reduceNumber(reduce::SquaredNorm);
T norm2 = norm2Result1->template t<T>(0);
delete norm2Result1;
if (norm2 > (T)0) *norm2View1Ptr /= math::sd_sqrt<T, T>(norm2);
delete norm2View1Ptr;
// normalize
NDArray *norm2View2Ptr = _Vecs({0, 0, j + 1, j + 2, 0, 0});
auto* norm2Result2 = norm2View2Ptr->reduceNumber(reduce::SquaredNorm);
norm2 = norm2Result2->template t<T>(0);
delete norm2Result2;
if (norm2 > (T)0) *norm2View2Ptr /= math::sd_sqrt<T, T>(norm2);
delete norm2View2Ptr;
++j;
}
}
}
template <typename T>
void EigenValsAndVecs<T>::calcEigenVecs(NDArray& schurMatrixU) {
calcEigenVecs_<T>(schurMatrixU, _Vals, _Vecs);
}
template <typename T>
void eig_(NDArray& input, NDArray& vals, NDArray& vecs) {
assert(input.rankOf() == 2 && "input is not a matrix");
assert(input.sizeAt(0) == input.sizeAt(1) && "input is not a square matrix");
assert(vals.rankOf() == 2 && vals.sizeAt(0) == input.sizeAt(0) && vals.sizeAt(1) == 2 &&
"incorrect shape for the eigenvalue results vals");
assert(vecs.rankOf() == 3 && vecs.sizeAt(0) == input.sizeAt(0) && vecs.sizeAt(1) == input.sizeAt(0) &&
vecs.sizeAt(2) == 2 && "incorrect shape for the eigenvector results vecs");
Schur<T> schur(input);
NDArray* schurMatrixU = schur.u;
NDArray* schurMatrixT = schur.t;
calcEigenVals_<T>(*schurMatrixT, vals);
calcPseudoEigenVecs_<T>(*schurMatrixT, *schurMatrixU, vals);
calcEigenVecs_<T>(*schurMatrixU, vals, vecs);
}
void eig(NDArray& input, NDArray& vals, NDArray& vecs) {
BUILD_SINGLE_SELECTOR(input.dataType(), eig_, (input, vals, vecs), SD_FLOAT_TYPES);
}
BUILD_SINGLE_TEMPLATE( void eig_, (NDArray& input, NDArray& vals, NDArray& vecs), SD_FLOAT_TYPES);
BUILD_SINGLE_TEMPLATE( class EigenValsAndVecs, , SD_FLOAT_TYPES);
} // namespace helpers
} // namespace ops
} // namespace sd