/* ****************************************************************************** * * * 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 #include #include namespace sd { namespace ops { namespace helpers { ////////////////////////////////////////////////////////////////////////// template Hessenberg::Hessenberg(NDArray* matrix) { if (matrix->rankOf() != 2) THROW_EXCEPTION("ops::helpers::Hessenberg constructor: input matrix must be 2D !"); if (matrix->sizeAt(0) == 1) { std::vector qShape = {1, 1}; _Q = new NDArray(matrix->ordering(),qShape, matrix->dataType(), matrix->getContext()); *_Q = 1; _H = matrix->dup(matrix->ordering()); return; } if (matrix->sizeAt(0) != matrix->sizeAt(1)) THROW_EXCEPTION("ops::helpers::Hessenberg constructor: input array must be 2D square matrix !"); _H = matrix->dup(matrix->ordering()); _Q = matrix->ulike(); evalData(); } ////////////////////////////////////////////////////////////////////////// template void Hessenberg::evalData() { const int rows = _H->sizeAt(0); std::vector coeffsShape = {rows - 1}; NDArray hhCoeffs(_H->ordering(), coeffsShape, _H->dataType(), _H->getContext()); // calculate _H for (LongType i = 0; i < rows - 1; ++i) { T coeff, norm; NDArray hRef = *_H; NDArray *tail1Ptr = hRef({i + 1, -1, i, i + 1}); NDArray tail1 = *tail1Ptr; delete tail1Ptr; NDArray *tail2Ptr = hRef({i + 2, -1, i, i + 1}, true); NDArray tail2 = *tail2Ptr; delete tail2Ptr; Householder::evalHHmatrixDataI(tail1, coeff, norm); NDArray *hViewPtr = hRef({0, 0, i, i + 1}); hViewPtr->template r(i + 1) = norm; delete hViewPtr; hhCoeffs.template r(i) = coeff; NDArray *bottomRightCornerPtr = hRef({i + 1, -1, i + 1, -1}, true); NDArray bottomRightCorner = *bottomRightCornerPtr; delete bottomRightCornerPtr; Householder::mulLeft(bottomRightCorner, tail2, coeff); NDArray *tail2Trans = tail2.transpose(); NDArray *rightColsPtr = hRef({0, 0, i + 1, -1}, true); NDArray rightCols = *rightColsPtr; delete rightColsPtr; Householder::mulRight(rightCols, *tail2Trans, coeff); delete tail2Trans; } // calculate _Q HHsequence hhSeq(_H, &hhCoeffs, 'u'); hhSeq._diagSize = rows - 1; hhSeq._shift = 1; hhSeq.applyTo_(_Q); // fill down with zeros starting at first subdiagonal _H->fillAsTriangular(0, -1, -1, *_H, 'l',false); } ////////////////////////////////////////////////////////////////////////// template Schur::Schur(NDArray& matrix) { if (matrix.rankOf() != 2) THROW_EXCEPTION("ops::helpers::Schur constructor: input matrix must be 2D !"); if (matrix.sizeAt(0) != matrix.sizeAt(1)) THROW_EXCEPTION("ops::helpers::Schur constructor: input array must be 2D square matrix !"); evalData(matrix); } ////////////////////////////////////////////////////////////////////////// template void Schur::evalData(NDArray& matrix) { auto res = matrix.reduceNumber(reduce::AMax); const T scale = res->template t(0); delete res; if (scale < DataTypeUtils::min_positive()) { t = matrix.ulike(); u = matrix.ulike(); t->nullify(); u->setIdentity(); return; } // perform Hessenberg decomposition NDArray *matrixScale = matrix / scale; Hessenberg hess(matrixScale); t = hess._H; u = hess._Q; calcFromHessenberg(); *t *= scale; delete matrixScale; } ////////////////////////////////////////////////////////////////////////// template void Schur::splitTwoRows(const int ind, const T shift) { const int numCols = t->sizeAt(1); T p = (T)0.5 * (t->t(ind - 1, ind - 1) - t->t(ind, ind)); T q = p * p + t->t(ind, ind - 1) * t->t(ind - 1, ind); t->r(ind, ind) += shift; t->r(ind - 1, ind - 1) += shift; if (q >= (T)0) { T z = math::sd_sqrt(math::sd_abs(q)); std::vector rotShape = {2, 2}; NDArray rotation(t->ordering(), rotShape, t->dataType(), t->getContext()); if (p >= (T)0) JacobiSVD::createJacobiRotationGivens(p + z, t->t(ind, ind - 1), rotation); else JacobiSVD::createJacobiRotationGivens(p - z, t->t(ind, ind - 1), rotation); NDArray tRef = *t; NDArray *rightColsPtr = tRef({0, 0, ind - 1, -1}); NDArray rightCols = *rightColsPtr; delete rightColsPtr; NDArray *rotT = rotation.transpose(); JacobiSVD::mulRotationOnLeft(ind - 1, ind, rightCols, *rotT); NDArray *topRowsPtr = tRef({0, ind + 1, 0, 0}); NDArray topRows = *topRowsPtr; delete topRowsPtr; JacobiSVD::mulRotationOnRight(ind - 1, ind, topRows, rotation); JacobiSVD::mulRotationOnRight(ind - 1, ind, *u, rotation); t->r(ind, ind - 1) = (T)0; delete rotT; } if (ind > 1) t->r(ind - 1, ind - 2) = (T)0; } ////////////////////////////////////////////////////////////////////////// template void Schur::calcShift(const int ind, const int iter, T& shift, NDArray& shiftVec) { // shiftVec has length = 3 shiftVec.r(0) = t->t(ind, ind); shiftVec.r(1) = t->t(ind - 1, ind - 1); shiftVec.r(2) = t->t(ind, ind - 1) * t->t(ind - 1, ind); if (iter == 10) { shift += shiftVec.t(0); for (int i = 0; i <= ind; ++i) t->r(i, i) -= shiftVec.t(0); T s = math::sd_abs(t->t(ind, ind - 1)) + math::sd_abs(t->t(ind - 1, ind - 2)); shiftVec.r(0) = T(0.75) * s; shiftVec.r(1) = T(0.75) * s; shiftVec.r(2) = T(-0.4375) * s * s; } if (iter == 30) { T s = (shiftVec.t(1) - shiftVec.t(0)) / T(2.0); s = s * s + shiftVec.t(2); if (s > T(0)) { s = math::sd_sqrt(s); if (shiftVec.t(1) < shiftVec.t(0)) s = -s; s = s + (shiftVec.t(1) - shiftVec.t(0)) / T(2.0); s = shiftVec.t(0) - shiftVec.t(2) / s; shift += s; for (int i = 0; i <= ind; ++i) t->r(i, i) -= s; shiftVec = T(0.964); } } } ////////////////////////////////////////////////////////////////////////// template void Schur::initFrancisQR(const int ind1, const int ind2, NDArray& shiftVec, int& ind3, NDArray& householderVec) { // shiftVec has length = 3 for (ind3 = ind2 - 2; ind3 >= ind1; --ind3) { const T mm = t->t(ind3, ind3); const T r = shiftVec.t(0) - mm; const T s = shiftVec.t(1) - mm; householderVec.r(0) = (r * s - shiftVec.t(2)) / t->t(ind3 + 1, ind3) + t->t(ind3, ind3 + 1); householderVec.r(1) = t->t(ind3 + 1, ind3 + 1) - mm - r - s; householderVec.r(2) = t->t(ind3 + 2, ind3 + 1); if (ind3 == ind1) break; const T lhs = t->t(ind3, ind3 - 1) * (math::sd_abs(householderVec.t(1)) + math::sd_abs(householderVec.t(2))); const T rhs = householderVec.t(0) * (math::sd_abs(t->t(ind3 - 1, ind3 - 1)) + math::sd_abs(mm) + math::sd_abs(t->t(ind3 + 1, ind3 + 1))); if (math::sd_abs(lhs) < DataTypeUtils::eps() * rhs) break; } } ////////////////////////////////////////////////////////////////////////// template void Schur::doFrancisQR(const int ind1, const int ind2, const int ind3, NDArray& householderVec) { if (!(ind2 >= ind1)) THROW_EXCEPTION( "ops::helpers::Schur:doFrancisQR: wrong input indexes, condition ind2 >= ind1 must be true !"); if (!(ind2 <= ind3 - 2)) THROW_EXCEPTION( "ops::helpers::Schur:doFrancisQR: wrong input indexes, condition iind2 <= ind3-2 must be true !"); const int numCols = t->sizeAt(1); NDArray tRef = *t; NDArray uRef = *u; for (int k = ind2; k <= ind3 - 2; ++k) { const bool firstIter = (k == ind2); T coeff, normX; std::vector tailShape = {2,1}; NDArray tail(t->ordering(),tailShape, t->dataType(), t->getContext()); NDArray *firstPtr = firstIter ? &householderVec : tRef({k, k + 3, k - 1, k}); NDArray first = *firstPtr; if (!firstIter) delete firstPtr; Householder::evalHHmatrixData(first, tail, coeff, normX); if (normX != T(0)) { if (firstIter && k > ind1) t->r(k, k - 1) = -t->t(k, k - 1); else if (!firstIter) t->r(k, k - 1) = normX; NDArray *block1Ptr = tRef({k, k + 3, k, numCols}, true); NDArray block1 = *block1Ptr; delete block1Ptr; Householder::mulLeft(block1, tail, coeff); NDArray *block2Ptr = tRef({0, math::sd_min(ind3, k + 3) + 1, k, k + 3}, true); NDArray block2 = *block2Ptr; delete block2Ptr; Householder::mulRight(block2, tail, coeff); NDArray *block3Ptr = uRef({0, numCols, k, k + 3}, true); NDArray block3 = *block3Ptr; delete block3Ptr; Householder::mulRight(block3, tail, coeff); } } T coeff, normX; std::vector tailShape = {1,1}; NDArray tail(t->ordering(), tailShape, t->dataType(), t->getContext()); NDArray *firstPtr = tRef({ind3 - 1, ind3 + 1, ind3 - 2, ind3 - 1}); NDArray first = *firstPtr; delete firstPtr; Householder::evalHHmatrixData(first, tail, coeff, normX); if (normX != T(0)) { t->r(ind3 - 1, ind3 - 2) = normX; NDArray *block1Ptr = tRef({ind3 - 1, ind3 + 1, ind3 - 1, numCols}, true); NDArray block1 = *block1Ptr; delete block1Ptr; Householder::mulLeft(block1, tail, coeff); NDArray *block2Ptr = tRef({0, ind3 + 1, ind3 - 1, ind3 + 1}, true); NDArray block2 = *block2Ptr; delete block2Ptr; Householder::mulRight(block2, tail, coeff); NDArray *block3Ptr = uRef({0, numCols, ind3 - 1, ind3 + 1}, true); NDArray block3 = *block3Ptr; delete block3Ptr; Householder::mulRight(block3, tail, coeff); } for (int i = ind2 + 2; i <= ind3; ++i) { t->r(i, i - 2) = T(0); if (i > ind2 + 2) t->r(i, i - 3) = T(0); } } ////////////////////////////////////////////////////////////////////////// template void Schur::calcFromHessenberg() { const int maxIters = _maxItersPerRow * t->sizeAt(0); const int numCols = t->sizeAt(1); int iu = numCols - 1; int iter = 0; int totalIter = 0; T shift = T(0); NDArray tRef = *t; NDArray uRef = *u; T norm = static_cast(0); for (int j = 0; j < numCols; ++j) { NDArray *viewPtr = tRef({0, math::sd_min(numCols, j + 2), j, j + 1}); auto sum = viewPtr->reduceNumber(reduce::ASum); norm += sum->template t(0); delete viewPtr; delete sum; } if (norm != T(0)) { while (iu >= 0) { const int il = getSmallSubdiagEntry(iu); if (il == iu) { t->r(iu, iu) = t->t(iu, iu) + shift; if (iu > 0) t->r(iu, iu - 1) = T(0); iu--; iter = 0; } else if (il == iu - 1) { splitTwoRows(iu, shift); iu -= 2; iter = 0; } else { std::vector shiftVecShape = {3}; NDArray householderVec(t->ordering(), shiftVecShape, t->dataType(), t->getContext()); NDArray shiftVec(t->ordering(), shiftVecShape, t->dataType(), t->getContext()); calcShift(iu, iter, shift, shiftVec); ++iter; ++totalIter; if (totalIter > maxIters) break; int im; initFrancisQR(il, iu, shiftVec, im, householderVec); doFrancisQR(il, im, iu, householderVec); } } } } BUILD_SINGLE_TEMPLATE( class Hessenberg, , SD_FLOAT_TYPES); BUILD_SINGLE_TEMPLATE( class Schur, , SD_FLOAT_TYPES); } // namespace helpers } // namespace ops } // namespace sd