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 }