File indexing completed on 2024-05-12 03:41:58
0001 /************************************************************************************* 0002 * Copyright (C) 2014 by Percy Camilo T. Aucahuasi <percy.camilo.ta@gmail.com> * 0003 * * 0004 * This program is free software; you can redistribute it and/or * 0005 * modify it under the terms of the GNU General Public License * 0006 * as published by the Free Software Foundation; either version 2 * 0007 * of the License, or (at your option) any later version. * 0008 * * 0009 * This program is distributed in the hope that it will be useful, * 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0012 * GNU General Public License for more details. * 0013 * * 0014 * You should have received a copy of the GNU General Public License * 0015 * along with this program; if not, write to the Free Software * 0016 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * 0017 *************************************************************************************/ 0018 0019 #include "eigencommands.h" 0020 0021 #include <QCoreApplication> 0022 0023 #include <Eigen/Core> 0024 #include <Eigen/Eigenvalues> 0025 0026 #include "expression.h" 0027 #include "value.h" 0028 #include "list.h" 0029 #include "matrix.h" 0030 0031 using Analitza::Expression; 0032 using Analitza::ExpressionType; 0033 0034 static const Eigen::MatrixXcd executeEigenSolver(const QList< Analitza::Expression >& args, bool computeEigenvectors, QStringList &errors) 0035 { 0036 const int nargs = args.size(); 0037 0038 if (nargs != 1) { 0039 errors.append(QCoreApplication::tr("Invalid parameter count for '%1'. Should have 1 parameter").arg(EigenvaluesCommand::id)); 0040 0041 return Eigen::MatrixXcd(); 0042 } 0043 0044 const Analitza::Matrix *matrix = static_cast<const Analitza::Matrix*>(args.first().tree()); 0045 0046 if (!matrix->isSquare()) { 0047 errors.append(QCoreApplication::tr("To use '%1' command the input matrix must be square").arg(EigenvaluesCommand::id)); 0048 0049 return Eigen::MatrixXcd(); 0050 } 0051 0052 const int m = matrix->rowCount(); 0053 const int n = matrix->columnCount(); 0054 0055 Eigen::MatrixXd realmatrix(m, n); 0056 0057 for (int i = 0; i < m; ++i) { 0058 for (int j = 0; j < n; ++j) { 0059 if (matrix->at(i,j)->type() == Analitza::Object::value) { 0060 const Analitza::Cn *entry = static_cast<const Analitza::Cn*>(matrix->at(i,j)); 0061 const Analitza::Cn::ValueFormat entryformat = entry->format(); 0062 0063 //Don't allow complex numbers 0064 if (entryformat == Analitza::Cn::Char || entryformat == Analitza::Cn::Real || 0065 entryformat == Analitza::Cn::Integer || entryformat == Analitza::Cn::Boolean) { 0066 realmatrix(i,j) = entry->value(); 0067 } else { 0068 errors.append(QCoreApplication::tr("Invalid parameter type in matrix entry (%1,%2) for '%3', it must be a number value") 0069 .arg(i).arg(j).arg(EigenvaluesCommand::id)); 0070 0071 return Eigen::MatrixXcd(); 0072 } 0073 } else { 0074 errors.append(QCoreApplication::tr("Invalid parameter type in matrix entry (%1,%2) for '%3', it must be a number value") 0075 .arg(i).arg(j).arg(EigenvaluesCommand::id)); 0076 0077 return Eigen::MatrixXcd(); 0078 } 0079 } 0080 } 0081 0082 Q_ASSERT(nargs > 0); 0083 0084 Eigen::EigenSolver<Eigen::MatrixXd> eigensolver; 0085 eigensolver.compute(realmatrix, computeEigenvectors); 0086 0087 Eigen::MatrixXcd ret; 0088 0089 if (computeEigenvectors) 0090 ret = eigensolver.eigenvectors(); 0091 else 0092 ret = eigensolver.eigenvalues(); 0093 0094 return ret; 0095 } 0096 0097 const QString EigenvaluesCommand::id = QStringLiteral("eigenvalues"); 0098 const ExpressionType EigenvaluesCommand::type = ExpressionType(ExpressionType::Lambda) 0099 .addParameter(ExpressionType(ExpressionType::Any, ExpressionType(ExpressionType::Matrix, ExpressionType(ExpressionType::Vector, ExpressionType(ExpressionType::Value), -2), -1))) 0100 .addParameter(ExpressionType(ExpressionType::List, ExpressionType(ExpressionType::Value))); 0101 0102 //TODO complex values as matrix entries too 0103 //TODO support endomorphisms over Rn too 0104 Expression EigenvaluesCommand::operator()(const QList< Analitza::Expression >& args) 0105 { 0106 Expression ret; 0107 0108 QStringList errors; 0109 const Eigen::MatrixXcd eigeninfo = executeEigenSolver(args, false, errors); 0110 0111 if (!errors.isEmpty()) { 0112 ret.addError(errors.first()); 0113 0114 return ret; 0115 } 0116 const int neigenvalues = eigeninfo.rows(); 0117 0118 Analitza::List *list = new Analitza::List; 0119 0120 for (int i = 0; i < neigenvalues; ++i) { 0121 const std::complex<double> eigenvalue = eigeninfo(i); 0122 const double realpart = eigenvalue.real(); 0123 const double imagpart = eigenvalue.imag(); 0124 0125 if (std::isnan(realpart) || std::isnan(imagpart)) { 0126 ret.addError(QCoreApplication::tr("Returned eigenvalue is NaN", "NaN means Not a Number, is an invalid float number")); 0127 0128 delete list; 0129 0130 return ret; 0131 } else if (std::isinf(realpart) || std::isinf(imagpart)) { 0132 ret.addError(QCoreApplication::tr("Returned eigenvalue is too big")); 0133 0134 delete list; 0135 0136 return ret; 0137 } else { 0138 bool isonlyreal = true; 0139 0140 if (std::isnormal(imagpart)) { 0141 isonlyreal = false; 0142 } 0143 0144 Analitza::Cn * eigenvalueobj = nullptr; 0145 0146 if (isonlyreal) { 0147 eigenvalueobj = new Analitza::Cn(realpart); 0148 } else { 0149 eigenvalueobj = new Analitza::Cn(realpart, imagpart); 0150 } 0151 0152 list->appendBranch(eigenvalueobj); 0153 } 0154 } 0155 0156 ret.setTree(list); 0157 0158 return ret; 0159 } 0160 0161 0162 const QString EigenvectorsCommand::id = QStringLiteral("eigenvectors"); 0163 const ExpressionType EigenvectorsCommand::type = EigenvaluesCommand::type; 0164 0165 //TODO complex values as matrix entries too 0166 //TODO support endomorphisms over Rn too 0167 Expression EigenvectorsCommand::operator()(const QList< Analitza::Expression >& args) 0168 { 0169 Expression ret; 0170 0171 QStringList errors; 0172 const Eigen::MatrixXcd eigeninfo = executeEigenSolver(args, true, errors); 0173 0174 if (!errors.isEmpty()) { 0175 ret.addError(errors.first()); 0176 0177 return ret; 0178 } 0179 const int neigenvectors = eigeninfo.rows(); 0180 0181 QScopedPointer<Analitza::List> list(new Analitza::List); 0182 0183 for (int j = 0; j < neigenvectors; ++j) { 0184 const Eigen::VectorXcd col = eigeninfo.col(j); 0185 QScopedPointer<Analitza::Vector> eigenvector(new Analitza::Vector(neigenvectors)); 0186 0187 for (int i = 0; i < neigenvectors; ++i) { 0188 const std::complex<double> eigenvalue = col(i); 0189 const double realpart = eigenvalue.real(); 0190 const double imagpart = eigenvalue.imag(); 0191 0192 if (std::isnan(realpart) || std::isnan(imagpart)) { 0193 ret.addError(QCoreApplication::tr("Returned eigenvalue is NaN", "NaN means Not a Number, is an invalid float number")); 0194 return ret; 0195 } else if (std::isinf(realpart) || std::isinf(imagpart)) { 0196 ret.addError(QCoreApplication::tr("Returned eigenvalue is too big")); 0197 return ret; 0198 } else { 0199 auto eigenvalueobj = !std::isnormal(imagpart) ? 0200 new Analitza::Cn(realpart) 0201 : new Analitza::Cn(realpart, imagpart); 0202 0203 eigenvector->appendBranch(eigenvalueobj); 0204 } 0205 } 0206 0207 list->appendBranch(eigenvector.take()); 0208 } 0209 0210 ret.setTree(list.take()); 0211 0212 return ret; 0213 }