/* ****************************************************************************** * * * 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 #include namespace sd { namespace ops { namespace helpers { ////////////////////////////////////////////////////////////////////////// template EigenValsAndVecs::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 schur(matrix); NDArray* schurMatrixU = schur.u; NDArray* schurMatrixT = schur.t; std::vector shape = {schurMatrixU->sizeAt(1), schurMatrixU->sizeAt(1), 2}; _Vecs = NDArray(matrix.ordering(), shape, matrix.dataType(), matrix.getContext()); std::vector 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 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(i + 1, i) == T(0.f)) { _Vals.r(i, 0) = schurMatrixT.t(i, i); // real part _Vals.r(i, 1) = T(0); // imaginary part if (!math::sd_isfin(_Vals.t(static_cast(i), static_cast(0)))) { THROW_EXCEPTION("ops::helpers::igenValsAndVec::calcEigenVals: got infinite eigen value !"); return; } ++i; } else { T p = T(0.5) * (schurMatrixT.t(i, i) - schurMatrixT.t(i + 1, i + 1)); T z; { T t0 = schurMatrixT.t(i + 1, i); T t1 = schurMatrixT.t(i, i + 1); T maxval = math::sd_max(math::sd_abs(p), math::sd_max(math::sd_abs(t0), math::sd_abs(t1))); t0 /= maxval; t1 /= maxval; T p0 = p / maxval; z = maxval * math::sd_sqrt(math::sd_abs(p0 * p0 + t0 * t1)); } _Vals.r(i, 0) = _Vals.r(i + 1, 0) = schurMatrixT.t(i + 1, i + 1) + p; _Vals.r(i, 1) = z; _Vals.r(i + 1, 1) = -z; if (!(math::sd_isfin(_Vals.t(i, 0)) && math::sd_isfin(_Vals.t(i + 1, 0)) && math::sd_isfin(_Vals.t(i, 1))) && math::sd_isfin(_Vals.t(i + 1, 1))) { THROW_EXCEPTION("ops::helpers::igenValsAndVec::calcEigenVals: got infinite eigen value !"); return; } i += 2; } } } template void EigenValsAndVecs::calcEigenVals(NDArray& schurMatrixT) { calcEigenVals_(schurMatrixT, _Vals); } ////////////////////////////////////////////////////////////////////////// template void calcPseudoEigenVecs_(NDArray& schurMatrixT, NDArray& schurMatrixU, NDArray& _Vals) { const int numOfCols = schurMatrixU.sizeAt(1); T norm = static_cast(0); for (int j = 0; j < numOfCols; ++j) { NDArray *viewPtr = schurMatrixT({j, j + 1, math::sd_max(j - 1, 0), numOfCols}); auto* reduceResult = viewPtr->reduceNumber(reduce::ASum); norm += reduceResult->template t(0); delete reduceResult; delete viewPtr; } if (norm == T(0)) return; for (int n = numOfCols - 1; n >= 0; n--) { T p = _Vals.t(n, 0); // real part T q = _Vals.t(n, 1); // imaginary part if (q == (T)0) { // not complex T lastr((T)0), lastw((T)0); int l = n; schurMatrixT.r(n, n) = T(1); for (int i = n - 1; i >= 0; i--) { T w = schurMatrixT.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(0); delete view1Ptr; delete view2Ptr; delete dotResult; if (_Vals.t(i, 1) < T(0)) { lastw = w; lastr = r; } else { l = i; if (_Vals.t(i, 1) == T(0)) { if (w != T(0)) schurMatrixT.r(i, n) = -r / w; else schurMatrixT.r(i, n) = -r / (DataTypeUtils::eps() * norm); } else { T x = schurMatrixT.t(i, i + 1); T y = schurMatrixT.t(i + 1, i); T denom = (_Vals.t(i, 0) - p) * (_Vals.t(i, 0) - p) + _Vals.t(i, 1) * _Vals.t(i, 1); T t = (x * lastr - lastw * r) / denom; schurMatrixT.r(i, n) = t; if (math::sd_abs(x) > math::sd_abs(lastw)) schurMatrixT.r(i + 1, n) = (-r - w * t) / x; else schurMatrixT.r(i + 1, n) = (-lastr - y * t) / lastw; } T t = math::sd_abs(schurMatrixT.t(i, n)); if ((DataTypeUtils::eps() * 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(schurMatrixT.t(n, n - 1)) > math::sd_abs(schurMatrixT.t(n - 1, n))) { schurMatrixT.r(n - 1, n - 1) = q / schurMatrixT.t(n, n - 1); schurMatrixT.r(n - 1, n) = -(schurMatrixT.t(n, n) - p) / schurMatrixT.t(n, n - 1); } else { EigenValsAndVecs::divideComplexNums(T(0), -schurMatrixT.t(n - 1, n), schurMatrixT.t(n - 1, n - 1) - p, q, schurMatrixT.r(n - 1, n - 1), schurMatrixT.r(n - 1, n)); } schurMatrixT.r(n, n - 1) = T(0); schurMatrixT.r(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(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(0); delete saView1Ptr; delete saView2Ptr; delete saDotResult; T w = schurMatrixT.t(i, i) - p; if (_Vals.t(i, 1) < T(0)) { lastw = w; lastra = ra; lastsa = sa; } else { l = i; if (_Vals.t(i, 1) == T(0)) { EigenValsAndVecs::divideComplexNums(-ra, -sa, w, q, schurMatrixT.r(i, n - 1), schurMatrixT.r(i, n)); } else { T x = schurMatrixT.t(i, i + 1); T y = schurMatrixT.t(i + 1, i); T vr = (_Vals.t(i, 0) - p) * (_Vals.t(i, 0) - p) + _Vals.t(i, 1) * _Vals.t(i, 1) - q * q; T vi = (_Vals.t(i, 0) - p) * T(2) * q; if ((vr == T(0)) && (vi == T(0))) vr = DataTypeUtils::eps() * norm * (math::sd_abs(w) + math::sd_abs(q) + math::sd_abs(x) + math::sd_abs(y) + math::sd_abs(lastw)); EigenValsAndVecs::divideComplexNums(x * lastra - lastw * ra + q * sa, x * lastsa - lastw * sa - q * ra, vr, vi, schurMatrixT.r(i, n - 1), schurMatrixT.r(i, n)); if (math::sd_abs(x) > (math::sd_abs(lastw) + math::sd_abs(q))) { schurMatrixT.r(i + 1, n - 1) = (-ra - w * schurMatrixT.t(i, n - 1) + q * schurMatrixT.t(i, n)) / x; schurMatrixT.r(i + 1, n) = (-sa - w * schurMatrixT.t(i, n) - q * schurMatrixT.t(i, n - 1)) / x; } else EigenValsAndVecs::divideComplexNums(-lastra - y * schurMatrixT.t(i, n - 1), -lastsa - y * schurMatrixT.t(i, n), lastw, q, schurMatrixT.r(i + 1, n - 1), schurMatrixT.r(i + 1, n)); } T t = math::sd_max(math::sd_abs(schurMatrixT.t(i, n - 1)), math::sd_abs(schurMatrixT.t(i, n))); if ((DataTypeUtils::eps() * 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 void EigenValsAndVecs::calcPseudoEigenVecs(NDArray& schurMatrixT, NDArray& schurMatrixU) { calcPseudoEigenVecs_(schurMatrixT, schurMatrixU, _Vals); } ////////////////////////////////////////////////////////////////////////// template void calcEigenVecs_(NDArray& schurMatrixU, NDArray& _Vals, NDArray& _Vecs) { const T precision = T(2) * DataTypeUtils::eps(); const int numOfCols = schurMatrixU.sizeAt(1); for (int j = 0; j < numOfCols; ++j) { if (math::sd_abs(_Vals.t(j, 1)) <= math::sd_abs(_Vals.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(0); delete norm2Result; if (norm2 > (T)0) *norm2ViewPtr /= math::sd_sqrt(norm2); delete norm2ViewPtr; } else { // complex for (int i = 0; i < numOfCols; ++i) { _Vecs.r(i, j, 0) = _Vecs.r(i, j + 1, 0) = schurMatrixU.t(i, j); _Vecs.r(i, j, 1) = schurMatrixU.t(i, j + 1); _Vecs.r(i, j + 1, 1) = -schurMatrixU.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(0); delete norm2Result1; if (norm2 > (T)0) *norm2View1Ptr /= math::sd_sqrt(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(0); delete norm2Result2; if (norm2 > (T)0) *norm2View2Ptr /= math::sd_sqrt(norm2); delete norm2View2Ptr; ++j; } } } template void EigenValsAndVecs::calcEigenVecs(NDArray& schurMatrixU) { calcEigenVecs_(schurMatrixU, _Vals, _Vecs); } template 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 schur(input); NDArray* schurMatrixU = schur.u; NDArray* schurMatrixT = schur.t; calcEigenVals_(*schurMatrixT, vals); calcPseudoEigenVecs_(*schurMatrixT, *schurMatrixU, vals); calcEigenVecs_(*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