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

336 lines
13 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/FullPivLU.h>
#include <helpers/HessenbergAndSchur.h>
#include <helpers/MmulHelper.h>
#include <helpers/Sqrtm.h>
#include <ops/declarable/helpers/lup.h>
namespace sd {
namespace ops {
namespace helpers {
//////////////////////////////////////////////////////////////////////////
template <typename T>
static void sqrtmQuasiTrianDiag(NDArray& matrixT, NDArray& sqrtT) {
const int rows = matrixT.sizeAt(0);
for (int i = 0; i < rows; i++) {
if (i == rows - 1 || matrixT.t<T>(i + 1, i) == (T)0) {
const auto elemT = matrixT.t<T>(i, i);
if (elemT < (T)0)
THROW_EXCEPTION(
"ops::helpers::Sqrtm::sqrtmQuasiTrianDiag: can't take sqrt of negative diagonal element of T matrix !");
sqrtT.r<T>(i, i) = math::sd_sqrt<T, T>(elemT);
} else {
NDArray *esViewPtr = matrixT({i, i + 2, i, i + 2}, true);
EigenValsAndVecs<T> es(*esViewPtr); // es._Vecs {2,2,2}, es._Vals{2,2}
delete esViewPtr;
NDArray& vecs = es._Vecs;
NDArray& vals = es._Vals;
const T& vecsReal00 = vecs.t<T>(0, 0, 0);
const T& vecsImag00 = vecs.t<T>(0, 0, 1);
const T& vecsReal01 = vecs.t<T>(0, 1, 0);
const T& vecsImag01 = vecs.t<T>(0, 1, 1);
const T& vecsReal10 = vecs.t<T>(1, 0, 0);
const T& vecsImag10 = vecs.t<T>(1, 0, 1);
const T& vecsReal11 = vecs.t<T>(1, 1, 0);
const T& vecsImag11 = vecs.t<T>(1, 1, 1);
// es.eigenvalues().cwiseSqrt().asDiagonal()
T eigenValsSqrt[2][2];
eigenValsSqrt[0][0] = vals.t<T>(0, 0);
eigenValsSqrt[0][1] = vals.t<T>(0, 1);
eigenValsSqrt[1][0] = vals.t<T>(1, 0);
eigenValsSqrt[1][1] = vals.t<T>(1, 1);
EigenValsAndVecs<T>::sqrtComplexNum(eigenValsSqrt[0][0], eigenValsSqrt[0][1]);
EigenValsAndVecs<T>::sqrtComplexNum(eigenValsSqrt[1][0], eigenValsSqrt[1][1]);
// es.eigenvectors() * es.eigenvalues().cwiseSqrt().asDiagonal()
T vecsElem[2][2][2];
EigenValsAndVecs<T>::multiplyComplexNums(vecsReal00, vecsImag00, eigenValsSqrt[0][0], eigenValsSqrt[0][1],
vecsElem[0][0][0], vecsElem[0][0][1]);
EigenValsAndVecs<T>::multiplyComplexNums(vecsReal01, vecsImag01, eigenValsSqrt[1][0], eigenValsSqrt[1][1],
vecsElem[0][1][0], vecsElem[0][1][1]);
EigenValsAndVecs<T>::multiplyComplexNums(vecsReal10, vecsImag10, eigenValsSqrt[0][0], eigenValsSqrt[0][1],
vecsElem[1][0][0], vecsElem[1][0][1]);
EigenValsAndVecs<T>::multiplyComplexNums(vecsReal11, vecsImag11, eigenValsSqrt[1][0], eigenValsSqrt[1][1],
vecsElem[1][1][0], vecsElem[1][1][1]);
// es.eigenvectors().inverse()
T vecsElemInv[2][2][2];
T tempReal, tempImag, divisorReal, divisorImag;
EigenValsAndVecs<T>::multiplyComplexNums(vecsReal00, vecsImag00, vecsReal11, vecsImag11, divisorReal,
divisorImag);
EigenValsAndVecs<T>::multiplyComplexNums(vecsReal01, vecsImag01, vecsReal10, vecsImag10, tempReal, tempImag);
divisorReal -= tempReal;
divisorImag -= tempImag;
EigenValsAndVecs<T>::divideComplexNums(vecsReal11, vecsImag11, divisorReal, divisorImag, vecsElemInv[0][0][0],
vecsElemInv[0][0][1]);
EigenValsAndVecs<T>::divideComplexNums(-vecsReal01, -vecsImag01, divisorReal, divisorImag, vecsElemInv[0][1][0],
vecsElemInv[0][1][1]);
EigenValsAndVecs<T>::divideComplexNums(-vecsReal10, -vecsImag10, divisorReal, divisorImag, vecsElemInv[1][0][0],
vecsElemInv[1][0][1]);
EigenValsAndVecs<T>::divideComplexNums(vecsReal00, vecsImag00, divisorReal, divisorImag, vecsElemInv[1][1][0],
vecsElemInv[1][1][1]);
// result
T result[2][2][2];
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[0][0][0], vecsElem[0][0][1], vecsElemInv[0][0][0],
vecsElemInv[0][0][1], tempReal, tempImag);
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[0][1][0], vecsElem[0][1][1], vecsElemInv[1][0][0],
vecsElemInv[1][0][1], result[0][0][0], result[0][0][1]);
result[0][0][0] += tempReal;
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[0][0][0], vecsElem[0][0][1], vecsElemInv[0][1][0],
vecsElemInv[0][1][1], tempReal, tempImag);
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[0][1][0], vecsElem[0][1][1], vecsElemInv[1][1][0],
vecsElemInv[1][1][1], result[0][1][0], result[0][1][1]);
result[0][1][0] += tempReal;
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[1][0][0], vecsElem[1][0][1], vecsElemInv[0][0][0],
vecsElemInv[0][0][1], tempReal, tempImag);
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[1][1][0], vecsElem[1][1][1], vecsElemInv[1][0][0],
vecsElemInv[1][0][1], result[1][0][0], result[1][0][1]);
result[1][0][0] += tempReal;
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[1][0][0], vecsElem[1][0][1], vecsElemInv[0][1][0],
vecsElemInv[0][1][1], tempReal, tempImag);
EigenValsAndVecs<T>::multiplyComplexNums(vecsElem[1][1][0], vecsElem[1][1][1], vecsElemInv[1][1][0],
vecsElemInv[1][1][1], result[1][1][0], result[1][1][1]);
result[1][1][0] += tempReal;
sqrtT.r<T>(i, i) = result[0][0][0];
sqrtT.r<T>(i, i + 1) = result[0][1][0];
sqrtT.r<T>(i + 1, i) = result[1][0][0];
sqrtT.r<T>(i + 1, i + 1) = result[1][1][0];
++i;
}
}
}
//////////////////////////////////////////////////////////////////////////
// all matrices are {2,2} here
template <typename T>
static void sqrtmQuasiTrianAuxEq(NDArray& A, NDArray& B, NDArray& C, NDArray& X) {
std::vector<LongType> tempShape = {4,4};
NDArray tempMatrix(A.ordering(),tempShape, A.dataType(), A.getContext());
tempMatrix.r<T>(0, 0) = A.t<T>(0, 0) + B.t<T>(0, 0);
tempMatrix.r<T>(1, 1) = A.t<T>(0, 0) + B.t<T>(1, 1);
tempMatrix.r<T>(2, 2) = A.t<T>(1, 1) + B.t<T>(0, 0);
tempMatrix.r<T>(3, 3) = A.t<T>(1, 1) + B.t<T>(1, 1);
tempMatrix.r<T>(0, 1) = B.t<T>(1, 0);
tempMatrix.r<T>(0, 2) = A.t<T>(0, 1);
tempMatrix.r<T>(1, 0) = B.t<T>(0, 1);
tempMatrix.r<T>(1, 3) = A.t<T>(0, 1);
tempMatrix.r<T>(2, 0) = A.t<T>(1, 0);
tempMatrix.r<T>(2, 3) = B.t<T>(1, 0);
tempMatrix.r<T>(3, 1) = A.t<T>(1, 0);
tempMatrix.r<T>(3, 2) = B.t<T>(0, 1);
tempMatrix.r<T>(0, 3) = (T)0;
tempMatrix.r<T>(1, 2) = (T)0;
tempMatrix.r<T>(2, 1) = (T)0;
tempMatrix.r<T>(3, 0) = (T)0;
std::vector<LongType> resultShape = {4,1};
NDArray result(A.ordering(), resultShape, A.dataType(), A.getContext());
result.r<T>(0, 0) = C.t<T>(0, 0);
result.r<T>(1, 0) = C.t<T>(0, 1);
result.r<T>(2, 0) = C.t<T>(1, 0);
result.r<T>(3, 0) = C.t<T>(1, 1);
FullPivLU<T>::solve(tempMatrix, result, result);
X.r<T>(0, 0) = result.t<T>(0);
X.r<T>(0, 1) = result.t<T>(1);
X.r<T>(1, 0) = result.t<T>(2);
X.r<T>(1, 1) = result.t<T>(3);
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
static void sqrtmQuasiTrianOffDiag(NDArray& matrixT, NDArray& sqrtT) {
const int rows = matrixT.sizeAt(0);
for (int j = 1; j < rows; j++) {
if (matrixT.t<T>(j, j - 1) != (T)0) continue;
for (int i = j - 1; i >= 0; i--) {
if (i > 0 && matrixT.t<T>(i, i - 1) != (T)0) continue;
const bool iBlockIs2x2 = (i < rows - 1) && (matrixT.t<T>(i + 1, i) != (T)0);
const bool jBlockIs2x2 = (j < rows - 1) && (matrixT.t<T>(j + 1, j) != (T)0);
if (iBlockIs2x2 && jBlockIs2x2) {
NDArray *APtr = sqrtT({i, i + 2, i, i + 2}, true);
NDArray A = *APtr;
delete APtr;
NDArray *BPtr = sqrtT({j, j + 2, j, j + 2}, true);
NDArray B = *BPtr;
delete BPtr;
NDArray *XPtr = matrixT({i, i + 2, j, j + 2}, true);
NDArray X = *XPtr;
delete XPtr;
if (j - i > 2) {
NDArray *leftPtr = sqrtT({i, i + 2, i + 2, j}, true);
NDArray *rightPtr = sqrtT({i + 2, j, j, j + 2}, true);
auto mul = mmul(*leftPtr, *rightPtr);
X -= *mul;
delete leftPtr;
delete rightPtr;
delete mul;
}
sqrtmQuasiTrianAuxEq<T>(A, B, X, X);
sqrtT.syncToDevice();
NDArray *assignPtr = sqrtT({i, i + 2, j, j + 2}, true);
assignPtr->assign(&X);
delete assignPtr;
} else if (iBlockIs2x2 && !jBlockIs2x2) {
NDArray *rhsPtr = matrixT({i, i + 2, j, j + 1}, true);
NDArray rhs = *rhsPtr;
delete rhsPtr;
if (j - i > 2) {
NDArray *leftPtr = sqrtT({i, i + 2, i + 2, j}, true);
NDArray *rightPtr = sqrtT({i + 2, j, j, j + 1}, true);
auto mul = mmul(*leftPtr, *rightPtr);
rhs -= *mul;
delete leftPtr;
delete rightPtr;
delete mul;
}
std::vector<LongType> aShape = {2,2};
NDArray A(matrixT.ordering(), aShape, matrixT.dataType(), matrixT.getContext());
A.r<T>(0, 0) = A.r<T>(1, 1) = sqrtT.t<T>(j, j);
A.r<T>(0, 1) = A.r<T>(1, 0) = T(0);
NDArray *addPtr = sqrtT({i, i + 2, i, i + 2}, true);
A += *addPtr;
delete addPtr;
FullPivLU<T>::solve(A, rhs, rhs);
// sqrtT.syncToDevice();
NDArray *assignPtr = sqrtT({i, i + 2, j, j + 1}, true);
assignPtr->assign(&rhs);
delete assignPtr;
} else if (!iBlockIs2x2 && jBlockIs2x2) {
NDArray *rhsPtr = matrixT({i, i + 1, j, j + 2}, true);
NDArray rhs = *rhsPtr;
delete rhsPtr;
if (j - i > 1) {
NDArray *leftPtr = sqrtT({i, i + 1, i + 1, j}, true);
NDArray *rightPtr = sqrtT({i + 1, j, j, j + 2}, true);
auto mul = mmul(*leftPtr, *rightPtr);
rhs -= *mul;
delete leftPtr;
delete rightPtr;
delete mul;
}
std::vector<LongType> aShape = {2,2};
NDArray A(matrixT.ordering(),aShape, matrixT.dataType(), matrixT.getContext());
A.r<T>(0, 0) = A.r<T>(1, 1) = sqrtT.t<T>(i, i);
A.r<T>(0, 1) = A.r<T>(1, 0) = T(0);
NDArray *addPtr = sqrtT({j, j + 2, j, j + 2}, true);
NDArray *add = addPtr->transpose();
delete addPtr;
A += *add;
delete add;
NDArray *rhsT = rhs.transpose();
FullPivLU<T>::solve(A, *rhsT, *rhsT);
// sqrtT.syncToDevice();
NDArray *assignPtr = sqrtT({i, i + 1, j, j + 2}, true);
assignPtr->assign(&rhs);
delete assignPtr;
delete rhsT;
} else if (!iBlockIs2x2 && !jBlockIs2x2) {
NDArray *leftPtr = sqrtT({i, i + 1, i + 1, j});
NDArray *rightPtr = sqrtT({i + 1, j, j, j + 1});
auto mul = mmul(*leftPtr, *rightPtr);
T temp = mul->t<T>(0); // dot
delete leftPtr;
delete rightPtr;
delete mul;
sqrtT.r<T>(i, j) = (matrixT.t<T>(i, j) - temp) / (sqrtT.t<T>(i, i) + sqrtT.t<T>(j, j));
}
}
}
}
//////////////////////////////////////////////////////////////////////////
template <typename T>
void Sqrtm<T>::calc(NDArray& in, NDArray& out) {
if (in.rankOf() != 2 || in.sizeAt(0) != in.sizeAt(1))
THROW_EXCEPTION("ops::helpers::Sqrtm::calc: input matrix must have rank 2 and be square !");
if (!out.isSameShape(in))
THROW_EXCEPTION("ops::helpers::Sqrtm::calc: output matrix must have the same shape as input one!");
if (in.lengthOf() == 1) {
out.r<T>(0) = math::sd_sqrt<T, T>(in.t<T>(0));
return;
}
Schur<T> schur(in);
NDArray *inULike = in.ulike();
NDArray sqrtT = *inULike;
sqrtT.nullify();
sqrtmQuasiTrianDiag<T>(*schur.t, sqrtT);
sqrtmQuasiTrianOffDiag<T>(*schur.t, sqrtT);
NDArray *second = schur.u->transpose();
// out = U * sqrtT * U^T;
NDArray *temp = mmul(sqrtT, *second);
MmulHelper::mmul(schur.u, temp, &out);
delete inULike;
delete second;
delete temp;
}
BUILD_SINGLE_TEMPLATE( class Sqrtm, , SD_FLOAT_TYPES);
} // namespace helpers
} // namespace ops
} // namespace sd