From 3bfe804035d7ec64756a6a2f2300aa4d124d2cfb Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sun, 7 Jul 2024 11:39:00 -0700 Subject: [PATCH 01/80] Added Koopman experimental features to expMSSA --- expui/expMSSA.H | 14 +++ expui/expMSSA.cc | 259 ++++++++++++++++++++++++++++++++++++++++++ pyEXP/MSSAWrappers.cc | 43 +++++++ 3 files changed, 316 insertions(+) diff --git a/expui/expMSSA.H b/expui/expMSSA.H index 1200f0d9..a5cdb160 100644 --- a/expui/expMSSA.H +++ b/expui/expMSSA.H @@ -50,6 +50,13 @@ namespace MSSA //! Right singular vectors Eigen::MatrixXd U; + //@{ + //! Koopman modes + Eigen::VectorXcd L; + Eigen::MatrixXcd Phi; + int window; + //@} + //! Parameters //@{ bool flip, verbose, powerf; @@ -280,6 +287,13 @@ namespace MSSA return ret; } + //! Estimate Koopman modes from the trajectory eigenvectors + std::tuple + getKoopmanModes(const double tol, int window, bool debug); + + //! Return the reconstructed Koopman modes + std::map getReconstructedKoopman(int mode); + }; diff --git a/expui/expMSSA.cc b/expui/expMSSA.cc index b136d2e7..7a19c6f6 100644 --- a/expui/expMSSA.cc +++ b/expui/expMSSA.cc @@ -1554,6 +1554,265 @@ namespace MSSA { } + std::tuple + expMSSA::getKoopmanModes(double tol, int D, bool debug) + { + bool use_fullKh = true; // Use the non-reduced computation of + // Koopman/eDMD + // Number of channels + // + nkeys = mean.size(); + + // Make sure parameters are sane + // + if (numW<=0) numW = numT/2; + if (numW > numT/2) numW = numT/2; + + numK = numT - numW + 1; + + Eigen::VectorXd S1; + Eigen::MatrixXd Y1; + Eigen::MatrixXd V1; + Eigen::MatrixXd VT1; + Eigen::MatrixXd VT2; + + // Make a new trajetory matrix with smoothing + // + Y1.resize(numK, numW*nkeys + D*(nkeys-1)); + + size_t n=0, offset=0; + for (auto k : mean) { + for (int i=0; i 0) { + // Back blending + for (int j=0; j(D-j)/D; + } + } + // Main series + for (int j=0; j(D-j)/D; + } + } + } + offset += numW + D; + n++; + } + + double Scale = Y1.norm(); + + // auto YY = Y1/Scale; + auto YY = Y1; + + // Use one of the built-in Eigen3 algorithms + // + /* + if (params["Jacobi"]) { + // -->Using Jacobi + Eigen::JacobiSVD + svd(YY, Eigen::ComputeThinU | Eigen::ComputeThinV); + S1 = svd.singularValues(); + V1 = svd.matrixV(); + } else if (params["BDCSVD"]) { + */ + // -->Using BDC + Eigen::BDCSVD + svd(YY, Eigen::ComputeFullU | Eigen::ComputeFullV); + // svd(YY, Eigen::ComputeThinU | Eigen::ComputeThinV); + S1 = svd.singularValues(); + V1 = svd.matrixV(); + /* + } else { + // -->Use Random approximation algorithm from Halko, Martinsson, + // and Tropp + int srank = std::min(YY.cols(), YY.rows()); + RedSVD::RedSVD svd(YY, srank); + S1 = svd.singularValues(); + V1 = svd.matrixV(); + } + */ + + std::cout << "shape V1 = " << V1.rows() << " x " + << V1.cols() << std::endl; + + std::cout << "shape Y1 = " << Y1.rows() << " x " + << Y1.cols() << std::endl; + + int lags = V1.rows(); + int rank = V1.cols(); + + std::ofstream out; + if (debug) out.open("debug.txt"); + + if (out) out << "rank=" << rank << " lags=" << lags << std::endl; + + VT1.resize(rank, lags-1); + VT2.resize(rank, lags-1); + + for (int j=0; j uu; + for (int i=0; iUsing Jacobi + Eigen::JacobiSVD + // svd(VT1, Eigen::ComputeThinU | Eigen::ComputeThinV); + svd(VT1, Eigen::ComputeFullU | Eigen::ComputeFullV); + SS = svd.singularValues(); + UU = svd.matrixU(); + VV = svd.matrixV(); + } else if (params["BDCSVD"]) { + */ + { + // -->Using BDC + Eigen::BDCSVD + // svd(VT1, Eigen::ComputeThinU | Eigen::ComputeThinV); + svd(VT1, Eigen::ComputeFullU | Eigen::ComputeFullV); + SS = svd.singularValues(); + UU = svd.matrixU(); + VV = svd.matrixV(); + } + /* + } else { + // -->Use Random approximation algorithm from Halko, Martinsson, + // and Tropp + // RedSVD::RedSVD svd(VT1, std::min(rank, numK-1)); + RedSVD::RedSVD svd(VT1, std::max(VT1.rows(), VT2.cols())); + SS = svd.singularValues(); + UU = svd.matrixU(); + VV = svd.matrixV(); + } + */ + + if (out) out << "Singular values" << std::endl << SS << std::endl; + + // Compute inverse + for (int i=0; i tol) SS(i) = 1.0/SS(i); + else SS(i) = 0.0; + } + + // Compute full Koopman operator + if (use_fullKh) { + + Eigen::MatrixXd DD(VV.cols(), UU.cols()); + DD.setZero(); + for (int i=0; i es(AT); + + L = es.eigenvalues(); + Phi = es.eigenvectors(); + + if (out) { + out << std::endl << "Eigenvalues" << std::endl << L << std::endl + << std::endl << "Eigenvectors" << std::endl << Phi << std::endl; + } + + } + // Compute the reduced Koopman operator + else { + + Eigen::MatrixXd AT = UU.transpose() * (VT2 * VV) * SS.asDiagonal(); + + // Compute spectrum + Eigen::EigenSolver es(AT, true); + + L = es.eigenvalues(); + auto W = es.eigenvectors(); + + // Compute the EDMD modes + // + Eigen::VectorXcd Linv(L); + for (int i=0; i tol) Linv(i) = 1.0/Linv(i); + else Linv(i) = 0.0; + } + + Phi = VT2 * VV * SS.asDiagonal() * W * Linv.asDiagonal(); + + if (out) { + out << std::endl << "Eigenvalues" << std::endl << L << std::endl + << std::endl << "Eigenvectors" << std::endl << Phi << std::endl; + } + } + + // Cache window size + // + window = D; + + return {L, Phi}; + } + + std::map + expMSSA::getReconstructedKoopman(int mode) + { + // Copy the original map for return + // + auto newdata = data; + + size_t n=0, offset=0; + + for (auto u : mean) { + + double disp = totVar; + if (type == TrendType::totPow) disp = totPow; + if (disp==0.0) disp = var[u.first]; + + std::complex phase = 1.0; + for (int i=0; i Date: Mon, 29 Jul 2024 17:11:06 -0400 Subject: [PATCH 02/80] Comment change only --- expui/expMSSA.H | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/expui/expMSSA.H b/expui/expMSSA.H index a5cdb160..a15cfeb4 100644 --- a/expui/expMSSA.H +++ b/expui/expMSSA.H @@ -51,7 +51,7 @@ namespace MSSA Eigen::MatrixXd U; //@{ - //! Koopman modes + //! Koopman modes with Hankel matrices Eigen::VectorXcd L; Eigen::MatrixXcd Phi; int window; From 82615b1722cdc5fd3569b9f26d1f801cad7b8950 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Tue, 30 Jul 2024 16:41:49 -0400 Subject: [PATCH 03/80] Added RKHS EDMD methods [no ci] --- expui/CMakeLists.txt | 2 +- expui/KoopmanRKHS.H | 170 +++++++++++++++ expui/KoopmanRKHS.cc | 466 ++++++++++++++++++++++++++++++++++++++++++ pyEXP/EDMDWrappers.cc | 111 ++++++++++ 4 files changed, 748 insertions(+), 1 deletion(-) create mode 100644 expui/KoopmanRKHS.H create mode 100644 expui/KoopmanRKHS.cc diff --git a/expui/CMakeLists.txt b/expui/CMakeLists.txt index 8690817f..d0a6c79d 100644 --- a/expui/CMakeLists.txt +++ b/expui/CMakeLists.txt @@ -34,7 +34,7 @@ endif() set(expui_SOURCES BasisFactory.cc BiorthBasis.cc FieldBasis.cc CoefContainer.cc CoefStruct.cc FieldGenerator.cc expMSSA.cc Coefficients.cc KMeans.cc Centering.cc ParticleIterator.cc - Koopman.cc BiorthBess.cc) + Koopman.cc KoopmanRKHS.cc BiorthBess.cc) add_library(expui ${expui_SOURCES}) set_target_properties(expui PROPERTIES OUTPUT_NAME expui) target_include_directories(expui PUBLIC ${common_INCLUDE}) diff --git a/expui/KoopmanRKHS.H b/expui/KoopmanRKHS.H new file mode 100644 index 00000000..e21bb591 --- /dev/null +++ b/expui/KoopmanRKHS.H @@ -0,0 +1,170 @@ +#ifndef EXP_KOOPMAN_RKHS_H +#define EXP_KOOPMAN_RKHS_H + +#include +#include "CoefContainer.H" + +#include + +namespace MSSA +{ + /** + Class for eDMD processing of EXP coefficients along with + user-specified auxiliary channels + */ + class KoopmanRKHS + { + + public: + + //! RKHS types + enum class RKHS + { + Polynomial, + Exponential, + Gaussian + }; + + //! For type reflection and parsing + static std::map RKHS_names; + static std::map RKHS_values; + + protected: + + //@{ + //! Repacked stream data for eDMD + std::map, mSSAkeyCompare > data; + //@} + + //! Coefficient container + CoefContainer coefDB; + + //! Working updated copy of DB + std::shared_ptr newDB; + + //! Parameter database + YAML::Node params; + + //! Primary Koopman analysis + void koopman_analysis(); + + bool computed, reconstructed; + + //! EDMD modes + Eigen::MatrixXcd Xi; + + //! DMD state matrices + Eigen::MatrixXd G, A, X; + + //! Eigenvalue tolerance + double tol = 1.0e-6; + + //! Eigenvalues values + Eigen::VectorXd S2, SP; + + //! Koopman eigenvalues + Eigen::VectorXcd SR, SL; + + //! Koopman operator estimate + Eigen::MatrixXd K; + + //! Eigenvectors + Eigen::MatrixXd Q; + + //! Left and right eigenvectors + Eigen::MatrixXcd U, V; + + //! Parameters + //@{ + bool verbose, powerf, project; + std::string prefix, config; + int nev; + //@} + + //! Construct YAML node from string + void assignParameters(const std::string pars); + + //! Number of channels + int nkeys; + + //! Number of points in the time series + int numT; + + //! Valid keys for YAML configurations + static const std::set valid_keys; + + //! RKHS parameters + double d, alpha, mu, sigma; + + //! RKHS type + RKHS rkhs = RKHS::Polynomial; + + //! RKHS kernel + double kernel(const Eigen::VectorXd& x, const Eigen::VectorXd& y); + + public: + + /** Constructor + + @param spec map/dictionary of tuples listing the Coefs object + and a list of keys + + @param flags is a string of YAML with changes for the default ' + flag values + + @param window is the the window length + @param maxEV is the maximum number of eigenvectors + + The map/dictionary has the following structure: + { + "mnemonic1": (Coefs1, [ [key11], [key12], [...], ...]), + "mnemonic2": (Coefs2, [ [key21], [key22], [...], ...]), + . + . + } + + where the mnemonic is choosen for convenience the set of keys + for each coefficient set, Coefs, specify the indices in the + dimensionaly specific the the Coefs instance itself. + E.g. harmonic and radial indicies for spherical and + cylindrical bases. + */ + KoopmanRKHS(const mssaConfig& spec, const std::string flags=""); + + //! Destructor + virtual ~KoopmanRKHS() {} + + //! Get the eigenvalues + Eigen::VectorXcd eigenvalues() + { + if (not computed) koopman_analysis(); + return SR; + } + + //! Return the EDMD modes, an approximation to the Koopman eigenfunctions + Eigen::MatrixXcd getModes() + { + if (not computed) koopman_analysis(); + return Xi; + } + + //! Save current MSSA state to an HDF5 file with the given prefix + void saveState(const std::string& prefix); + + //! Restore current MSSA state to an HDF5 file with the given prefix + void restoreState(const std::string& prefix); + + //! Provides a list of all channel keys + std::vector getAllKeys() + { + std::vector ret; + for (auto v : data) ret.push_back(v.first); + return ret; + } + }; + + +} +// END namespace MSSA + +#endif diff --git a/expui/KoopmanRKHS.cc b/expui/KoopmanRKHS.cc new file mode 100644 index 00000000..78e9d640 --- /dev/null +++ b/expui/KoopmanRKHS.cc @@ -0,0 +1,466 @@ +// +// EDMD (Koopman theory) for EXP coefficients +// +// Uses fixed-rank approximation for the SVD to save time and space. +// Uses the approximate SVD randomized algorithm from Halko, +// Martinsson, and Tropp by default. Use BDCSVD or Jacobi flags for +// the standard methods. Using the randomized method together with +// trajectory matrix rather than covariance matrix analysis may lead +// to large errors in eigenvectors. +// +// The implementation here follows: +// +// M. O. Williams, C. W. Rowley, I. G. Kevrekidis, 2015, "A +// kernel-based method for data-driven Koopman spectral analysis", +// Journal of Computational Dynamics, 2 (2), 247-265 +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +/* For debugging + #undef eigen_assert + #define eigen_assert(x) \ + if (!(x)) { throw (std::runtime_error("Eigen assert error")); } +*/ + +#include + +#include + +#include +#include +#include +#include +#include + +namespace MSSA { + + // Helper ostream manipulator for debug coefficient key info + std::ostream& operator<< (std::ostream& out, const std::vector& t); + + std::map KoopmanRKHS::RKHS_names = + { + {KoopmanRKHS::RKHS::Polynomial, "Polynomial" }, + {KoopmanRKHS::RKHS::Exponential, "Exponential"}, + {KoopmanRKHS::RKHS::Gaussian, "Gaussian" } + }; + + std::map KoopmanRKHS::RKHS_values = + { + {"Polynomial", KoopmanRKHS::RKHS::Polynomial }, + {"Exponential", KoopmanRKHS::RKHS::Exponential}, + {"Gaussian", KoopmanRKHS::RKHS::Gaussian } + }; + + + //! RKHS kernel + double KoopmanRKHS::kernel(const Eigen::VectorXd& x, const Eigen::VectorXd& y) + { + if (rkhs == RKHS::Polynomial) { + double prod = x.adjoint()*y; + return pow(1.0 + prod/(d*d), alpha); + } else if (rkhs == RKHS::Exponential) { + double prod = x.adjoint()*y; + return exp(mu*prod); + } else if (rkhs == RKHS::Gaussian) { + auto diff = x - y; + double prod = diff.adjoint()*diff; + return exp(-prod/(sigma*sigma)); + } else { + throw std::runtime_error("KoopmanRKHS: Unknown kernel type"); + } + } + + // Algorithm 3 from Williams, Rowley, Kevrekidis + // + void KoopmanRKHS::koopman_analysis() + { + // Number of channels + // + auto keys = getAllKeys(); + nkeys = keys.size(); + + numT = data[keys[0]].size(); + + G.resize(numT, numT); + A.resize(numT, numT); + X.resize(numT, nkeys); + + for (int k=0; k eigensolver(G); + if (eigensolver.info() != Eigen::Success) { + throw std::runtime_error("KoopmanRKHS: Eigen solver failed"); + } + S2 = eigensolver.eigenvalues(); + Q = eigensolver.eigenvectors(); + + // Find inversion tolerance condition + // + SP.resize(S2.size()); + double TOL = S2(S2.size()-1) * tol; + int nev = 0; + for (int n=0; n eigensolver2(K), eigensolver3(K.transpose()); + SR = eigensolver2.eigenvalues(); + V = eigensolver2.eigenvectors(); + SL = eigensolver3.eigenvalues(); + U = eigensolver3.eigenvectors(); + + Xi = (U.transpose()*SP.asDiagonal()*Q.transpose()*X).transpose(); + + computed = true; + reconstructed = false; + } + + const std::set + KoopmanRKHS::valid_keys = { + "d", + "alpha", + "mu", + "sigma", + "verbose", + "output" + }; + + void KoopmanRKHS::assignParameters(const std::string flags) + { + // Default parameter values + // + d = 1.0; + alpha = 10.0; + mu = 1.0; + sigma = 1.0; + verbose = false; + + // Parse the parameters database + // + try { + + // Load parameters from string + // + params = YAML::Load(flags); + + // Check for unmatched keys + // + auto unmatched = YamlCheck(params, valid_keys); + if (unmatched.size()) + throw YamlConfigError("MSSA::KoopmanRKHS", "parameter", unmatched, __FILE__, __LINE__); + + // Compute flags + // + computed = false; + reconstructed = false; + + // Top level parameter flags + // + d = double(params["d" ].as()); + alpha = double(params["alpha" ].as()); + mu = double(params["mu" ].as()); + sigma = double(params["sigma" ].as()); + verbose = bool (params["verbose"]); + + if (params["output"] ) prefix = params["output"].as(); + else prefix = "exp_rkhs"; + + } + catch (const YAML::ParserException& e) { + std::cout << "KoopmanRKHS::assignParameters, parsing error=" << e.what() + << std::endl; + throw; + } + } + + + // Save current KOOPMAN state to an HDF5 file with the given prefix + void KoopmanRKHS::saveState(const std::string& prefix) + { + if (not computed) return; // No point in saving anything + + if (std::filesystem::exists(prefix + "_rkhs.h5")) { + std::ostringstream sout; + sout << "KoopmanRKHS::saveState: the file <" << prefix + "_edmd.h5" + << "> already exists.\nPlease delete this file or choose a " + << "different file name"; + throw std::runtime_error(sout.str()); + } + + try { + // Create a new hdf5 file + // + HighFive::File file(prefix + "_rkhs.h5", + HighFive::File::ReadWrite | + HighFive::File::Create); + + // Write the time dimension + // + file.createAttribute("numT", HighFive::DataSpace::From(numT)).write(numT); + + // Write the number of channels + // + file.createAttribute("nKeys", HighFive::DataSpace::From(nkeys)).write(nkeys); + + // Write the number of eigenvalues + // + file.createAttribute("nEV", HighFive::DataSpace::From(nev)).write(nev); + + // Save the key list + // + std::vector keylist; + for (auto k : data) keylist.push_back(k.first); + + // Pad keylist entries to maximum key length for HighFive + // + size_t maxSZ = 0; + for (auto v : keylist) maxSZ = std::max(maxSZ, v.size()); + + // The padding value + // + auto padVal = std::numeric_limits::max(); + for (auto & v : keylist) { + if (v.size() < maxSZ) { + for (auto k=v.size(); k keylist; + h5file.getDataSet("keylist").read(keylist); + + // Remove padded values from K5 store + // + auto padVal = std::numeric_limits::max(); + for (auto & v : keylist) { + std::vector::iterator it; + while ((it = std::find(v.begin(), v.end(), padVal)) != v.end()) + v.erase(it); + } + + // Check key list + // + bool bad = false; + for (int n=0; nfirst.size() != keylist[n].size()) bad = true; + else { + for (int i=0; ifirst[i]) bad = true; + } + } + } + + if (bad) { + std::ostringstream sout; + sout << "KoopmanRKHS::restoreState: keylist mismatch." << std::endl + << "Can't restore KoopmanRKHS state! Wanted keylist: "; + for (auto v : data) { + sout << "["; + for (auto u : v.first) sout << u << ' '; + sout << "] "; + } + sout << std::endl << "Found keylist: "; + for (auto v : keylist) { + sout << "["; + for (auto u : v) sout << u << ' '; + sout << "] "; + } + + throw std::runtime_error(sout.str()); + } + + auto analysis = h5file.getGroup("koopmanRKHS_analysis"); + + std::string type = analysis.getDataSet("rkhs").read(); + rkhs = RKHS_values[type]; + + Xi = analysis.getDataSet("Xi" ).read(); + G = analysis.getDataSet("G" ).read(); + A = analysis.getDataSet("A" ).read(); + X = analysis.getDataSet("X" ).read(); + S2 = analysis.getDataSet("S2" ).read(); + SP = analysis.getDataSet("SP" ).read(); + SR = analysis.getDataSet("SR" ).read(); + SL = analysis.getDataSet("SL" ).read(); + Q = analysis.getDataSet("Q" ).read(); + K = analysis.getDataSet("K" ).read(); + U = analysis.getDataSet("U" ).read(); + V = analysis.getDataSet("V" ).read(); + nev = analysis.getDataSet("nev").read(); + + computed = true; + + } catch (HighFive::Exception& err) { + std::cerr << "**** Error opening or reading H5 file ****" << std::endl; + throw; + } + + } + + KoopmanRKHS::KoopmanRKHS(const mssaConfig& config, const std::string flags) + { + // Parse the YAML string + // + assignParameters(flags); + + // Eigen OpenMP reporting + // + static bool firstTime = true; + if (firstTime) { + std::cout << "---- Eigen is using " << Eigen::nbThreads() + << " threads" << std::endl; + firstTime = false; + } + + // Now open and parse the coefficient files + // + coefDB = CoefContainer(config, flags); + + numT = coefDB.times.size(); + + // Generate all the channels + // + for (auto key : coefDB.getKeys()) { + data[key] = coefDB.getData(key); + } + + computed = false; + reconstructed = false; + } + // END KoopmanRKHS constructor + +} +// END namespace MSSA diff --git a/pyEXP/EDMDWrappers.cc b/pyEXP/EDMDWrappers.cc index 6a7f2553..a712aa34 100644 --- a/pyEXP/EDMDWrappers.cc +++ b/pyEXP/EDMDWrappers.cc @@ -3,6 +3,7 @@ #include #include +#include namespace py = pybind11; #include @@ -292,4 +293,114 @@ void EDMDtoolkitClasses(py::module &m) { -------- Koopman )"); + + py::class_> g(m, "KoopmanRKHS"); + + g.def(py::init(), + R"( + Koopman RKHS operator approximatation class + + Parameters + ---------- + config : mssaConfig + the input database of components + flag : str + YAML stanza of parameter values + + Returns + ------- + Koopman instance + + Notes + ----- + The configuration should be in the format: + + {'example': (coefs, keylst, [])} + + where keylst is a list of selected PCs. With a SphericalBasis + for example, the keylst should have the format: + + [[l1, m1, n1], [l2, m2, n2], ...] + + Each sublist represents a PC, where l, m, and n are the + spherical harmonics basis parameters. + )", + py::arg("config"), + py::arg("flags") = ""); + + g.def("eigenvalues", &KoopmanRKHS::eigenvalues, + R"( + Vector of eigenvalues from the EDMD analysis. + + Returns + ------- + numpy.ndarray + the vector of eigenvalues + + Notes + ----- + Note that these eigenvalues are complex. It may be helpful + to separate the magnitude and phase using Numpy's 'absolute' + and 'angle' functions. + )"); + + g.def("saveState", &KoopmanRKHS::saveState, + R"( + Save current EDMD state to an HDF5 file with the given prefix + + Parameters + ---------- + prefix : str + output filename prefix + + Returns + ------- + None + )", py::arg("prefix")); + + g.def("restoreState", &KoopmanRKHS::restoreState, + R"( + Restore current EDMD state from an HDF5 file + + Parameters + ---------- + prefix : str + input filename prefix + + Notes + ----- + The Koopman instance must be constructed with the same data and parameters + as the saved state. The restoreState routine will check for the same + data dimension and trend state but can not sure complete consistency. + )", py::arg("prefix")); + + g.def("getModes", &KoopmanRKHS::getModes, + R"( + Access to detrended reconstructed channel series. + + Returns + ------- + numpy.ndarray + the EDMD modes + + See also + -------- + Use in conjunction with 'contributions' to visualize the support + from each EDMD mode to the coefficient series. + )"); + + g.def("getAllKeys", &KoopmanRKHS::getAllKeys, + R"( + Provides a list of all internal channel keys (for reference) + + Returns + ------- + list(Key) + list of keys in the format described in config file + + See also + -------- + Koopman + )"); + } From caec62e14f38c312c5e6f23afc44a1af4514a7ee Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Wed, 7 Aug 2024 15:49:37 -0400 Subject: [PATCH 04/80] Bug fixes for KoopmanRKHS [no ci] --- expui/KoopmanRKHS.H | 29 +++- expui/KoopmanRKHS.cc | 329 +++++++++++++++++++++++++++++++++++++----- pyEXP/EDMDWrappers.cc | 80 ++++++++-- 3 files changed, 378 insertions(+), 60 deletions(-) diff --git a/expui/KoopmanRKHS.H b/expui/KoopmanRKHS.H index e21bb591..28115836 100644 --- a/expui/KoopmanRKHS.H +++ b/expui/KoopmanRKHS.H @@ -46,10 +46,17 @@ namespace MSSA YAML::Node params; //! Primary Koopman analysis - void koopman_analysis(); + void analysis(); bool computed, reconstructed; + //@{ + //! Test stuff + double lam=0.9, mu=0.5, c=0.0; + bool oscil = true, testF = true; + //@} + + //! EDMD modes Eigen::MatrixXcd Xi; @@ -94,13 +101,14 @@ namespace MSSA static const std::set valid_keys; //! RKHS parameters - double d, alpha, mu, sigma; + double d, alpha; //! RKHS type RKHS rkhs = RKHS::Polynomial; //! RKHS kernel - double kernel(const Eigen::VectorXd& x, const Eigen::VectorXd& y); + double kernel(const Eigen::VectorXd& x, + const Eigen::VectorXd& y); public: @@ -129,7 +137,7 @@ namespace MSSA E.g. harmonic and radial indicies for spherical and cylindrical bases. */ - KoopmanRKHS(const mssaConfig& spec, const std::string flags=""); + KoopmanRKHS(const mssaConfig& spec, double tol, const std::string flags=""); //! Destructor virtual ~KoopmanRKHS() {} @@ -137,17 +145,23 @@ namespace MSSA //! Get the eigenvalues Eigen::VectorXcd eigenvalues() { - if (not computed) koopman_analysis(); + if (not computed) analysis(); return SR; } - //! Return the EDMD modes, an approximation to the Koopman eigenfunctions + //! Return the Koopman modes, the coefficients to the eigenfunctions Eigen::MatrixXcd getModes() { - if (not computed) koopman_analysis(); + if (not computed) analysis(); return Xi; } + //! Evaluate the contribution from the indexed triple + Eigen::VectorXcd modeEval(int index, const Eigen::VectorXd& x); + + //! Return the eigenfunction + std::complex evecEval(int index, const Eigen::VectorXd& x); + //! Save current MSSA state to an HDF5 file with the given prefix void saveState(const std::string& prefix); @@ -163,7 +177,6 @@ namespace MSSA } }; - } // END namespace MSSA diff --git a/expui/KoopmanRKHS.cc b/expui/KoopmanRKHS.cc index 78e9d640..e875809c 100644 --- a/expui/KoopmanRKHS.cc +++ b/expui/KoopmanRKHS.cc @@ -1,3 +1,5 @@ +#define TESTING + // // EDMD (Koopman theory) for EXP coefficients // @@ -70,26 +72,73 @@ namespace MSSA { //! RKHS kernel - double KoopmanRKHS::kernel(const Eigen::VectorXd& x, const Eigen::VectorXd& y) + double KoopmanRKHS::kernel(const Eigen::VectorXd& x, + const Eigen::VectorXd& y) { if (rkhs == RKHS::Polynomial) { double prod = x.adjoint()*y; return pow(1.0 + prod/(d*d), alpha); } else if (rkhs == RKHS::Exponential) { double prod = x.adjoint()*y; - return exp(mu*prod); + return exp(prod/(d*d)); } else if (rkhs == RKHS::Gaussian) { - auto diff = x - y; + Eigen::VectorXd diff = x - y; double prod = diff.adjoint()*diff; - return exp(-prod/(sigma*sigma)); + return exp(-prod/(d*d)); } else { throw std::runtime_error("KoopmanRKHS: Unknown kernel type"); } } + // Structure for sorting complex numbers and retrieving a permutation index + using complex_elem = std::pair, int>; + bool complex_comparator(const complex_elem &lhs, const complex_elem &rhs) + { + auto a = lhs.first; + auto b = rhs.first; + + double abs_a = std::abs(a); + double abs_b = std::abs(b); + + if (abs_a == abs_b) { + if (std::real(a) == std::real(b)) + return std::imag(a) < std::imag(b); + else + return std::real(a) < std::real(b); + } else { + return abs_a < abs_b; + } + } + + void matrix_print(std::ostream& out, const Eigen::MatrixXcd& M) + { + for (int i=0; i eigensolver2(K), eigensolver3(K.transpose()); - SR = eigensolver2.eigenvalues(); - V = eigensolver2.eigenvectors(); - SL = eigensolver3.eigenvalues(); - U = eigensolver3.eigenvectors(); + Eigen::EigenSolver eigensolver2(K); + Eigen::EigenSolver eigensolver3(K.adjoint()); + + // Right and left eigenvectors + // + Eigen::VectorXcd SR2 = eigensolver2.eigenvalues().conjugate(); + Eigen::MatrixXcd V2 = eigensolver2.eigenvectors(); + + Eigen::VectorXcd SL2 = eigensolver3.eigenvalues(); + Eigen::MatrixXcd U2 = eigensolver3.eigenvectors(); + + U = U2; + V = V2; + + SL = SL2; + SR = SR2; + + { + std::vector v(SR2.size()); + for (int i=0; i u(SL2.size()); + for (int i=0; i KoopmanRKHS::valid_keys = { "d", "alpha", - "mu", - "sigma", + "oscil", "testF", "lam", "mu", "c", + "kernel", "verbose", "output" }; + template + std::string get_shape(const Eigen::EigenBase& x) + { + std::ostringstream oss; + oss << "(" << x.rows() << ", " << x.cols() << ")"; + return oss.str(); + } + + Eigen::VectorXcd KoopmanRKHS::modeEval(int index, const Eigen::VectorXd& x) + { + if (not computed) analysis(); + + Eigen::VectorXcd ret(nkeys); + ret.setZero(); + + if (index>=0 and index < numT) { + Eigen::VectorXd Y(numT); + for (int j=0; j Psi = Y.adjoint()*Q*SP.asDiagonal()*V.col(index); + + ret = Xi.col(index)*Psi; + } + + return ret; + } + + std::complex KoopmanRKHS::evecEval(int index, const Eigen::VectorXd& x) + { + if (not computed) analysis(); + + static bool first = true; + if (first) { + std::ofstream test("test.data"); + test << "Q" << std::endl << Q << std::endl; + test << "SP" << std::endl << SP << std::endl; + test << "V" << std::endl << V << std::endl; + first = false; + } + + std::complex ret(0.0); + + if (index>=0 and index < numT) { + Eigen::VectorXd Y(numT); + for (int j=0; j()); - alpha = double(params["alpha" ].as()); - mu = double(params["mu" ].as()); - sigma = double(params["sigma" ].as()); - verbose = bool (params["verbose"]); + if (params["d"]) + d = double(params["d"].as()); + + if (params["lam"]) + lam = double(params["lam"].as()); + + if (params["mu"]) + mu = double(params["mu"].as()); + + if (params["c"]) + c = double(params["c"].as()); + + if (params["oscil"]) + oscil = bool(params["oscil"].as()); + + if (params["testF"]) + testF = bool(params["testF"].as()); + + if (params["alpha"]) + alpha = double(params["alpha"].as()); + + if (params["kernel"]) { + std::string type = params["kernel"].as(); + if (RKHS_values.find(type) != RKHS_values.end()) + rkhs = RKHS_values[type]; + else + throw std::runtime_error("KoopmanRKHS: kernel type [" + type + + "] not found"); + } + + if (params["verbose"]) + verbose = bool(params["verbose"]); - if (params["output"] ) prefix = params["output"].as(); - else prefix = "exp_rkhs"; + if (params["output"]) prefix = params["output"].as(); + else prefix = "exp_rkhs"; } catch (const YAML::ParserException& e) { @@ -430,7 +685,7 @@ namespace MSSA { } - KoopmanRKHS::KoopmanRKHS(const mssaConfig& config, const std::string flags) + KoopmanRKHS::KoopmanRKHS(const mssaConfig& config, double tol, const std::string flags) : tol(tol) { // Parse the YAML string // diff --git a/pyEXP/EDMDWrappers.cc b/pyEXP/EDMDWrappers.cc index a712aa34..22bd2f71 100644 --- a/pyEXP/EDMDWrappers.cc +++ b/pyEXP/EDMDWrappers.cc @@ -130,7 +130,8 @@ void EDMDtoolkitClasses(py::module &m) { using namespace MSSA; - py::class_> f(m, "Koopman"); + py::class_> + f(m, "Koopman"); f.def(py::init(), R"( @@ -294,22 +295,25 @@ void EDMDtoolkitClasses(py::module &m) { Koopman )"); - py::class_> g(m, "KoopmanRKHS"); + py::class_> + g(m, "KoopmanRKHS"); - g.def(py::init(), + g.def(py::init(), R"( Koopman RKHS operator approximatation class Parameters ---------- config : mssaConfig - the input database of components + the input database of components + tol : double + tolerance for eigenvalue decomposition flag : str YAML stanza of parameter values Returns ------- - Koopman instance + Koopman RKHS instance Notes ----- @@ -326,11 +330,12 @@ void EDMDtoolkitClasses(py::module &m) { spherical harmonics basis parameters. )", py::arg("config"), + py::arg("tol"), py::arg("flags") = ""); g.def("eigenvalues", &KoopmanRKHS::eigenvalues, R"( - Vector of eigenvalues from the EDMD analysis. + Vector of eigenvalues from the EDMD RKHS analysis. Returns ------- @@ -346,7 +351,7 @@ void EDMDtoolkitClasses(py::module &m) { g.def("saveState", &KoopmanRKHS::saveState, R"( - Save current EDMD state to an HDF5 file with the given prefix + Save current EDMD RKHS state to an HDF5 file with the given prefix Parameters ---------- @@ -360,7 +365,7 @@ void EDMDtoolkitClasses(py::module &m) { g.def("restoreState", &KoopmanRKHS::restoreState, R"( - Restore current EDMD state from an HDF5 file + Restore current EDMD RKHS state from an HDF5 file Parameters ---------- @@ -369,26 +374,71 @@ void EDMDtoolkitClasses(py::module &m) { Notes ----- - The Koopman instance must be constructed with the same data and parameters - as the saved state. The restoreState routine will check for the same - data dimension and trend state but can not sure complete consistency. + The Koopman RKHS instance must be constructed with the same data and + parameters as the saved state. The restoreState routine will check + for the same data dimension and trend state but can not sure + complete consistency. )", py::arg("prefix")); g.def("getModes", &KoopmanRKHS::getModes, R"( - Access to detrended reconstructed channel series. + Get the RKHS mode coefficients for all triples Returns ------- numpy.ndarray - the EDMD modes + the RKHS mode coefficients See also -------- Use in conjunction with 'contributions' to visualize the support - from each EDMD mode to the coefficient series. + from each EDMD RKHS mode to the coefficient series. )"); + g.def("modeEval", &KoopmanRKHS::modeEval, + R"( + Evaluate the contribution from the index triple + + Parameters + ---------- + index : int + the triple index in eigenvalue order + value : ndarray + the input point + + Returns + ------- + numpy.ndarray + the contribution to the trajectory from the indexed triple + + See also + -------- + Use in conjunction with 'contributions' to visualize the support + from each EDMD RKHS mode to the coefficient series. + )", py::arg("index"), py::arg("value")); + + g.def("evecEval", &KoopmanRKHS::evecEval, + R"( + Evaluate the Koopman eigenfunction with given index + + Parameters + ---------- + index : int + the triple index in eigenvalue order + value : ndarray + the input point + + Returns + ------- + numpy.ndarray + the Koopman eigenfunction + + See also + -------- + Use in conjunction with 'contributions' to visualize the support + from each EDMD RKHS mode to the coefficient series. + )", py::arg("index"), py::arg("value")); + g.def("getAllKeys", &KoopmanRKHS::getAllKeys, R"( Provides a list of all internal channel keys (for reference) @@ -400,7 +450,7 @@ void EDMDtoolkitClasses(py::module &m) { See also -------- - Koopman + KoopmanRKHS )"); } From b1cacf2e9cd2ed59eab038c1f11caac5af71ad33 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sun, 11 Aug 2024 12:34:22 -0400 Subject: [PATCH 05/80] Some additional orbit tests --- expui/KoopmanRKHS.H | 4 +-- expui/KoopmanRKHS.cc | 69 +++++++++++++++++++++++++++++++++++++------- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/expui/KoopmanRKHS.H b/expui/KoopmanRKHS.H index 28115836..b6231e34 100644 --- a/expui/KoopmanRKHS.H +++ b/expui/KoopmanRKHS.H @@ -52,8 +52,8 @@ namespace MSSA //@{ //! Test stuff - double lam=0.9, mu=0.5, c=0.0; - bool oscil = true, testF = true; + double lam=0.9, mu=0.5, c=0.0, tscale=1.0; + bool plummer = true, radial = false, oscil = false, testF = true; //@} diff --git a/expui/KoopmanRKHS.cc b/expui/KoopmanRKHS.cc index e875809c..a602f59d 100644 --- a/expui/KoopmanRKHS.cc +++ b/expui/KoopmanRKHS.cc @@ -121,7 +121,7 @@ namespace MSSA { } } - void matrix_idtest(std::ostream& out, const Eigen::MatrixXcd& M) + bool matrix_idtest(std::ostream& out, const Eigen::MatrixXcd& M) { double max_diag = 0.0, max_offd = 0.0; for (int i=0; i Psi = Y.adjoint()*Q*SP.asDiagonal()*V.col(index); + std::complex Psi = Y.transpose()*Q*SP.asDiagonal()*V.col(index); ret = Xi.col(index)*Psi; } @@ -403,7 +441,7 @@ namespace MSSA { Y(j) = kernel(x, X.row(j)); } - ret = Y.adjoint()*Q*SP.asDiagonal()*V.col(index); + ret = Y.transpose()*Q*SP.asDiagonal()*V.col(index); } return ret; @@ -450,6 +488,15 @@ namespace MSSA { if (params["c"]) c = double(params["c"].as()); + if (params["plummer"]) + plummer = bool(params["plummer"].as()); + + if (params["radial"]) + radial = bool(params["radial"].as()); + + if (params["tscale"]) + tscale = double(params["tscale"].as()); + if (params["oscil"]) oscil = bool(params["oscil"].as()); From 031b5ff2029afc0f3e444945ad7e4b98ab1584ae Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 12 Aug 2024 13:31:20 -0400 Subject: [PATCH 06/80] Added Kepler test to KoopmanRKHS --- expui/KoopmanRKHS.H | 3 ++- expui/KoopmanRKHS.cc | 24 ++++++++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/expui/KoopmanRKHS.H b/expui/KoopmanRKHS.H index b6231e34..a546d5e3 100644 --- a/expui/KoopmanRKHS.H +++ b/expui/KoopmanRKHS.H @@ -53,7 +53,8 @@ namespace MSSA //@{ //! Test stuff double lam=0.9, mu=0.5, c=0.0, tscale=1.0; - bool plummer = true, radial = false, oscil = false, testF = true; + bool kepler = true, plummer = false, radial = false, oscil = false; + bool testF = true; //@} diff --git a/expui/KoopmanRKHS.cc b/expui/KoopmanRKHS.cc index a602f59d..05652ad1 100644 --- a/expui/KoopmanRKHS.cc +++ b/expui/KoopmanRKHS.cc @@ -181,7 +181,24 @@ namespace MSSA { } if (testF) { - if (plummer) { + if (kepler) { + for (int l=0; l()); + if (params["kepler"]) + kepler = bool(params["kepler"].as()); + if (params["plummer"]) plummer = bool(params["plummer"].as()); From 477db5053ba9b2410f6084ab6c1bc5527eeeea35 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Tue, 20 Aug 2024 14:22:35 -0400 Subject: [PATCH 07/80] Comment and output formating changes only --- expui/KoopmanRKHS.cc | 65 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/expui/KoopmanRKHS.cc b/expui/KoopmanRKHS.cc index 05652ad1..a74f19b5 100644 --- a/expui/KoopmanRKHS.cc +++ b/expui/KoopmanRKHS.cc @@ -10,7 +10,7 @@ // trajectory matrix rather than covariance matrix analysis may lead // to large errors in eigenvectors. // -// The implementation here follows: +// The implementation here is based on the // // M. O. Williams, C. W. Rowley, I. G. Kevrekidis, 2015, "A // kernel-based method for data-driven Koopman spectral analysis", @@ -54,8 +54,11 @@ namespace MSSA { // Helper ostream manipulator for debug coefficient key info + // std::ostream& operator<< (std::ostream& out, const std::vector& t); + // Get RKHS name from enum + // std::map KoopmanRKHS::RKHS_names = { {KoopmanRKHS::RKHS::Polynomial, "Polynomial" }, @@ -63,6 +66,8 @@ namespace MSSA { {KoopmanRKHS::RKHS::Gaussian, "Gaussian" } }; + // Get RKHS enum from name + // std::map KoopmanRKHS::RKHS_values = { {"Polynomial", KoopmanRKHS::RKHS::Polynomial }, @@ -71,7 +76,8 @@ namespace MSSA { }; - //! RKHS kernel + // RKHS kernel + // double KoopmanRKHS::kernel(const Eigen::VectorXd& x, const Eigen::VectorXd& y) { @@ -91,6 +97,7 @@ namespace MSSA { } // Structure for sorting complex numbers and retrieving a permutation index + // using complex_elem = std::pair, int>; bool complex_comparator(const complex_elem &lhs, const complex_elem &rhs) { @@ -100,6 +107,11 @@ namespace MSSA { double abs_a = std::abs(a); double abs_b = std::abs(b); + // Three step sort: + // (1) By modulus + // (2) By real value + // (3) By imag value + // if (abs_a == abs_b) { if (std::real(a) == std::real(b)) return std::imag(a) < std::imag(b); @@ -110,6 +122,8 @@ namespace MSSA { } } + // Fixed-width matrix element printing + // void matrix_print(std::ostream& out, const Eigen::MatrixXcd& M) { for (int i=0; i Date: Fri, 23 Aug 2024 10:06:46 -0400 Subject: [PATCH 08/80] Fix nEV storage [no ci] --- expui/KoopmanRKHS.cc | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/expui/KoopmanRKHS.cc b/expui/KoopmanRKHS.cc index a74f19b5..7d3f05b1 100644 --- a/expui/KoopmanRKHS.cc +++ b/expui/KoopmanRKHS.cc @@ -289,14 +289,14 @@ namespace MSSA { S2 = eigensolver.eigenvalues(); Q = eigensolver.eigenvectors(); - // Find inversion tolerance condition (Eigen eigenvalues returned - // in increasing order) + // Find inversion tolerance condition (Eigen's eigenvalues + // returned in increasing order) // SP.resize(S2.size()); // This will be the Penrose // pseudoinverse double TOL = S2(S2.size()-1) * tol; TOL = tol; - int nev = 0; // Cache the number of non-zero EVs + nev = 0; // Cache the number of non-zero EVs for (int n=0; n keylist; h5file.getDataSet("keylist").read(keylist); From bdaa0ab9208b6ac626980c134bbd623b93fe6a0b Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sat, 24 Aug 2024 12:34:19 -0400 Subject: [PATCH 09/80] Update to RKHS constructor and additional stdout diagnostics [no ci] --- expui/KoopmanRKHS.H | 7 +++++-- expui/KoopmanRKHS.cc | 38 +++++++++++++++++++++++++++++--------- pyEXP/EDMDWrappers.cc | 5 ++++- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/expui/KoopmanRKHS.H b/expui/KoopmanRKHS.H index a546d5e3..30caccba 100644 --- a/expui/KoopmanRKHS.H +++ b/expui/KoopmanRKHS.H @@ -54,7 +54,7 @@ namespace MSSA //! Test stuff double lam=0.9, mu=0.5, c=0.0, tscale=1.0; bool kepler = true, plummer = false, radial = false, oscil = false; - bool testF = true; + bool testF = true, use_red = true; //@} @@ -67,6 +67,9 @@ namespace MSSA //! Eigenvalue tolerance double tol = 1.0e-6; + //! Max eigenvalue count + int evCount = 100; + //! Eigenvalues values Eigen::VectorXd S2, SP; @@ -138,7 +141,7 @@ namespace MSSA E.g. harmonic and radial indicies for spherical and cylindrical bases. */ - KoopmanRKHS(const mssaConfig& spec, double tol, const std::string flags=""); + KoopmanRKHS(const mssaConfig& spec, double tol, int count, const std::string flags=""); //! Destructor virtual ~KoopmanRKHS() {} diff --git a/expui/KoopmanRKHS.cc b/expui/KoopmanRKHS.cc index 7d3f05b1..0dfa1904 100644 --- a/expui/KoopmanRKHS.cc +++ b/expui/KoopmanRKHS.cc @@ -1,5 +1,4 @@ #define TESTING - // // EDMD (Koopman theory) for EXP coefficients // @@ -282,29 +281,46 @@ namespace MSSA { // Perform eigenanalysis of Grammian matrix (pos def) // - Eigen::SelfAdjointEigenSolver eigensolver(G); - if (eigensolver.info() != Eigen::Success) { - throw std::runtime_error("KoopmanRKHS: Eigensolver for Grammian failed"); + if (use_red) { + RedSVD::RedSymEigen eigensolver(G, evCount); + S2 = eigensolver.eigenvalues(); + Q = eigensolver.eigenvectors(); + } else { + Eigen::SelfAdjointEigenSolver eigensolver(G); + if (eigensolver.info() != Eigen::Success) { + throw std::runtime_error("KoopmanRKHS: Eigensolver for Grammian failed"); + } + S2 = eigensolver.eigenvalues(); + Q = eigensolver.eigenvectors(); } - S2 = eigensolver.eigenvalues(); - Q = eigensolver.eigenvectors(); // Find inversion tolerance condition (Eigen's eigenvalues // returned in increasing order) // + Eigen::MatrixXd cumS(S2); + for (int n=cumS.size()-2; n>=0; n--) cumS(n) += cumS(n+1); + bool firstNZ = true; + SP.resize(S2.size()); // This will be the Penrose // pseudoinverse double TOL = S2(S2.size()-1) * tol; TOL = tol; nev = 0; // Cache the number of non-zero EVs - for (int n=0; n(0, S2.size()-evCount); n()); + if (params["use_red"]) + use_red = bool(params["use_red"].as()); + if (params["alpha"]) alpha = double(params["alpha"].as()); @@ -778,7 +797,8 @@ namespace MSSA { } - KoopmanRKHS::KoopmanRKHS(const mssaConfig& config, double tol, const std::string flags) : tol(tol) + KoopmanRKHS::KoopmanRKHS(const mssaConfig& config, double tol, int count, + const std::string flags) : tol(tol), evCount(count) { // Parse the YAML string // diff --git a/pyEXP/EDMDWrappers.cc b/pyEXP/EDMDWrappers.cc index 22bd2f71..88893b79 100644 --- a/pyEXP/EDMDWrappers.cc +++ b/pyEXP/EDMDWrappers.cc @@ -298,7 +298,7 @@ void EDMDtoolkitClasses(py::module &m) { py::class_> g(m, "KoopmanRKHS"); - g.def(py::init(), + g.def(py::init(), R"( Koopman RKHS operator approximatation class @@ -308,6 +308,8 @@ void EDMDtoolkitClasses(py::module &m) { the input database of components tol : double tolerance for eigenvalue decomposition + count : int + top count of eigenvalues flag : str YAML stanza of parameter values @@ -331,6 +333,7 @@ void EDMDtoolkitClasses(py::module &m) { )", py::arg("config"), py::arg("tol"), + py::arg("count"), py::arg("flags") = ""); g.def("eigenvalues", &KoopmanRKHS::eigenvalues, From a223c606c03a29c87882994772188f7d8f9ab8d0 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sat, 24 Aug 2024 13:05:51 -0400 Subject: [PATCH 10/80] More precision in diagonstic output [no ci] --- expui/KoopmanRKHS.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/expui/KoopmanRKHS.cc b/expui/KoopmanRKHS.cc index 0dfa1904..1e246d1e 100644 --- a/expui/KoopmanRKHS.cc +++ b/expui/KoopmanRKHS.cc @@ -312,7 +312,8 @@ namespace MSSA { } else { if (firstNZ) { std::cout << "Koopman::analysis(): cumulation fraction=" - << cumS(n) / cumS(0) + << std::setprecision(10) << cumS(n) / cumS(0) + << std::scientific << std::setprecision(6) << " [" << (cumS(0) - cumS(n))/cumS(0) << "]" << std::endl; firstNZ = false; } From 9e58b5779380afbfac90840b764827b5f9b56a0e Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sun, 25 Aug 2024 18:20:00 -0400 Subject: [PATCH 11/80] Updates; incomplete implementation [no ci] --- expui/CMakeLists.txt | 2 +- expui/CoefStruct.H | 48 +++ expui/CoefStruct.cc | 63 ++++ expui/Coefficients.H | 106 ++++++ expui/Coefficients.cc | 278 ++++++++++++++ expui/KoopmanRKHS.cc | 3 + expui/LiouvilleRKHS.H | 192 ++++++++++ expui/LiouvilleRKHS.cc | 810 +++++++++++++++++++++++++++++++++++++++++ pyEXP/EDMDWrappers.cc | 162 +++++++++ 9 files changed, 1663 insertions(+), 1 deletion(-) create mode 100644 expui/LiouvilleRKHS.H create mode 100644 expui/LiouvilleRKHS.cc diff --git a/expui/CMakeLists.txt b/expui/CMakeLists.txt index d0a6c79d..bcee1c2c 100644 --- a/expui/CMakeLists.txt +++ b/expui/CMakeLists.txt @@ -34,7 +34,7 @@ endif() set(expui_SOURCES BasisFactory.cc BiorthBasis.cc FieldBasis.cc CoefContainer.cc CoefStruct.cc FieldGenerator.cc expMSSA.cc Coefficients.cc KMeans.cc Centering.cc ParticleIterator.cc - Koopman.cc KoopmanRKHS.cc BiorthBess.cc) + Koopman.cc KoopmanRKHS.cc LiouvilleRKHS.cc BiorthBess.cc) add_library(expui ${expui_SOURCES}) set_target_properties(expui PROPERTIES OUTPUT_NAME expui) target_include_directories(expui PUBLIC ${common_INCLUDE}) diff --git a/expui/CoefStruct.H b/expui/CoefStruct.H index b50bb46f..d438dc40 100644 --- a/expui/CoefStruct.H +++ b/expui/CoefStruct.H @@ -321,6 +321,53 @@ namespace CoefClasses }; + //! Specialization of CoefStruct for collection of trajectories + class TrajStruct : public CoefStruct + { + public: + //! Coefficient map type + using coefType = Eigen::Map; + + //! Coefficient map + std::shared_ptr coefs; + + //! Number of trajectories + int traj; + + //! Phase-space rank + int rank; + + //! Constructor + TrajStruct() : traj(0), rank(0) { geom = "trajectory"; } + + //! No need to read + bool read(std::istream& in, bool exp_type, bool verbose=false); + + //! Create an empty data storage + void create(); + + //! Copy all of data contents to a new instance + std::shared_ptr deepcopy(); + + //! Assign array + void assign(const Eigen::MatrixXd& arr) + { + traj = arr.rows(); + rank = arr.cols(); + + allocate(); + *coefs = arr; + } + + //! Allocate storage arrays + void allocate() + { + store.resize(traj*rank); + coefs = std::make_shared(store.data(), traj, rank); + } + + }; + //! Specialization of CoefStruct for spheres for general fields class SphFldStruct : public CoefStruct { @@ -441,6 +488,7 @@ namespace CoefClasses using SlabStrPtr = std::shared_ptr ; using CubeStrPtr = std::shared_ptr ; using TblStrPtr = std::shared_ptr ; + using TrajStrPtr = std::shared_ptr ; using SphFldPtr = std::shared_ptr ; using PlrFldPtr = std::shared_ptr ; diff --git a/expui/CoefStruct.cc b/expui/CoefStruct.cc index 20cb6b93..3537bde3 100644 --- a/expui/CoefStruct.cc +++ b/expui/CoefStruct.cc @@ -70,6 +70,14 @@ namespace CoefClasses throw std::runtime_error("TblStruct::create: cols must be >0"); } + void TrajStruct::create() + { + if (traj>0 and rank>0) + coefs = std::make_shared(store.data(), traj, rank); + else + throw std::runtime_error("TrajStruct::create: number of trajectories and phase-space size must be >0"); + } + void SphFldStruct::create() { if (nfld>0 and nmax>0) @@ -177,6 +185,23 @@ namespace CoefClasses return ret; } + std::shared_ptr TrajStruct::deepcopy() + { + auto ret = std::make_shared(); + + copyfields(ret); + + assert(("TrajStruct::deepcopy dimension mismatch", + traj*rank == store.size())); + + + ret->coefs = std::make_shared(ret->store.data(), traj, rank); + ret->traj = traj; + ret->rank = rank; + + return ret; + } + std::shared_ptr SphFldStruct::deepcopy() { auto ret = std::make_shared(); @@ -515,6 +540,44 @@ namespace CoefClasses } } + bool TrajStruct::read(std::istream& in, bool exp_type, bool verbose) + { + std::vector row; + std::string line; + + if (std::getline(in, line)) { + + std::istringstream sin(line); + + // Read first value as time + // + sin >> time; + + // If okay, read the rest of the line as columns + // + double val; + while (1) { + sin >> val; + if (sin) row.push_back(val); + else break; + } + + // Number of cols + int cols = row.size(); + + if (traj*rank != cols) + throw std::runtime_error("TrajStruct::read: data != traj*rank"); + + store.resize(cols); + coefs = std::make_shared(store.data(), traj, rank); + for (int i=0; i coefs; + + //! An alternate packing + std::vector data; + + //! Read the coefficients + virtual void readNativeCoefs(const std::string& file, + int stride, double tmin, double tmax); + + //! Get the YAML config + virtual std::string getYAML(); + + //! Write parameter attributes + virtual void WriteH5Params(HighFive::File& file); + + //! Write coefficient data in H5 + virtual unsigned WriteH5Times(HighFive::Group& group, unsigned count); + + public: + + //! Constructor + TrajectoryData(bool verbose=false) : Coefs("table", verbose) {} + + //! Constructor from vectors + TrajectoryData(const std::vector& time, + const std::vector& data, + bool verbose=false); + + //! Constructor from file + TrajectoryData(std::string& file, bool verbose=false); + + //! H5 constructor + TrajectoryData(HighFive::File& file, int stride=1, + double tmin=-std::numeric_limits::max(), + double tmax= std::numeric_limits::max(), + bool verbose=false); + + //! Copy constructor + TrajectoryData(TrajectoryData& p) : Coefs(p) + { + coefs = p.coefs; + data = p.data; + times = p.times; + } + + //! Clear coefficient container + virtual void clear() { coefs.clear(); } + + //! Add a coefficient structure to the container + virtual void add(CoefStrPtr coef); + + //! Get coefficient data at given time + virtual Eigen::VectorXcd& getData(double time); + + /** Set coefficient matrix at given time. This is for pybind11, + since the operator() will not allow lvalue assignment, it + seems. */ + virtual void setData(double time, const Eigen::VectorXcd& arr); + + void setMatrix(double time, const Eigen::MatrixXd& arr) + { setData(time, arr); } + + //! Get coefficient structure at a given time + virtual std::shared_ptr getCoefStruct(double time) + { return coefs[roundTime(time)]; } + + //! Get list of coefficient times + virtual std::vector Times() { return times; } + + //! Get all coefficients indexed in column, time + Eigen::Tensor getAllCoefs(); + + /** Get power for the series. Not implemented meaningfully for + trajctory data. */ + virtual Eigen::MatrixXd& Power + (int min=0, int max=std::numeric_limits::max()) + { + power.resize(0, 0); + return power; + } + + //! Make keys for the remaining indices in a subspace + virtual std::vector makeKeys(); + virtual std::vector makeKeys(Key k) { return makeKeys(); } + + //! Compare two collections of stanzas (this is for testing only) + virtual bool CompareStanzas(std::shared_ptr check); + + //! Copy all of the data; make new instances of shared pointer + //! objects. This will recursively call the deepcopy() functions + //! or all dependent objects + virtual std::shared_ptr deepcopy(); + + virtual void zerodata() { + for (auto v : coefs) v.second->zerodata(); + for (auto & v : data) v.setZero(); + } + + }; + /** Derived class for spherical field coefficients */ class SphFldCoefs : public Coefs { diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index 81862376..f272d96d 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -259,6 +259,26 @@ namespace CoefClasses } + std::shared_ptr TrajectoryData::deepcopy() + { + auto ret = std::make_shared(); + + // Copy the base-class fields + copyfields(ret); + + // Copy the local structures from the map to the struct pointers + // by copyfing fields, not the pointer + for (auto v : coefs) + ret->coefs[v.first] = + std::dynamic_pointer_cast(v.second->deepcopy()); + + ret->data = data; + ret->times = times; + + return ret; + } + + SphFldCoefs::SphFldCoefs(HighFive::File& file, int stride, double Tmin, double Tmax, bool verbose) : Coefs("sphere", verbose) @@ -1879,6 +1899,256 @@ namespace CoefClasses } + TrajectoryData::TrajectoryData(const std::vector& Times, + const std::vector& data, + bool verbose) : + data(data), Coefs("table", verbose) + { + times = Times; + for (int i=0; i(); + c->time = times[i]; + c->traj = data[i].rows(); + c->rank = data[i].cols(); + c->store.resize(c->traj*c->rank); + c->coefs = std::make_shared(c->store.data(), c->traj, c->rank); + *c->coefs = data[i]; + coefs[roundTime(c->time)] = c; + } + } + + TrajectoryData::TrajectoryData(std::string& file, bool verbose) : + Coefs("trajectory", verbose) + { + std::ifstream in(file); + + if (not in) { + throw std::runtime_error("TrajectoryData ERROR (runtime) opening file <" + file + ">"); + } + + while (in) { + TrajStrPtr c = std::make_shared(); + if (not c->read(in, verbose)) break; + + coefs[roundTime(c->time)] = c; + } + + for (auto c : coefs) times.push_back(c.first); + } + + + TrajectoryData::TrajectoryData(HighFive::File& file, int stride, + double Tmin, double Tmax, bool verbose) : + Coefs("trajectory", verbose) + { + int traj, rank; + unsigned count; + std::string config; + + file.getAttribute("name" ).read(name); + file.getAttribute("traj" ).read(traj); + file.getAttribute("rank" ).read(rank); + file.getAttribute("config").read(config); + file.getDataSet ("count" ).read(count); + + auto snaps = file.getGroup("snapshots"); + + snaps.getDataSet("times").read(times); + snaps.getDataSet("datatable").read(data); + + for (unsigned n=0; n Tmax) continue; + + // Pack the data into the coefficient variable + // + auto coef = std::make_shared(); + coef->traj = traj; + coef->rank = rank; + coef->time = times[n]; + coef->store.resize(traj*rank); + coef->coefs = std::make_shared + (coef->store.data(), traj, rank); + *coef->coefs = data[n]; + + coefs[roundTime(times[n])] = coef; + } + } + + Eigen::VectorXcd& TrajectoryData::getData(double time) + { + auto it = coefs.find(roundTime(time)); + + if (it == coefs.end()) { + + arr.resize(0); + + } else { + + arr = it->second->store; + + } + + return arr; + } + + void TrajectoryData::setData(double time, const Eigen::VectorXcd& dat) + { + auto it = coefs.find(roundTime(time)); + + if (it == coefs.end()) { + std::ostringstream str; + str << "TrajectoryData::setMatrix: requested time=" << time << " not found"; + throw std::runtime_error(str.str()); + } else { + it->second->store = dat; + it->second->coefs = std::make_shared + (it->second->store.data(), it->second->traj, it->second->rank); + + } + } + + void TrajectoryData::readNativeCoefs(const std::string& file, int stride, + double tmin, double tmax) + { + std::ifstream in(file); + + if (not in) { + throw std::runtime_error("TrajectoryData ERROR (runtime) opening file <" + file + ">"); + } + + int count = 0; + while (in) { + TrajStrPtr c = std::make_shared(); + if (not c->read(in, verbose)) break; + + if (count++ % stride) continue; + if (c->time < tmin or c->time > tmax) continue; + + coefs[roundTime(c->time)] = c; + } + + times.clear(); + for (auto t : coefs) times.push_back(t.first); + + if (myid==0) + std::cerr << "---- Coefs::factory: " + << "read ascii and created TrajectoryData" + << std::endl; + } + + + void TrajectoryData::WriteH5Params(HighFive::File& file) + { + int traj = coefs.begin()->second->traj; + int rank = coefs.begin()->second->rank; + + std::string geometry = "trajectory"; + + file.createAttribute("traj", HighFive::DataSpace::From(traj)).write(traj); + file.createAttribute("rank", HighFive::DataSpace::From(rank)).write(rank); + } + + unsigned TrajectoryData::WriteH5Times(HighFive::Group& snaps, unsigned count) + { + snaps.createDataSet("times", times); + snaps.createDataSet("datatable", data); + + count = times.size(); + + return count; + } + + + std::string TrajectoryData::getYAML() + { + std::string ret; + if (coefs.size()) { + ret = coefs.begin()->second->buf; + } + return ret; + } + + bool TrajectoryData::CompareStanzas(std::shared_ptr check) + { + bool ret = true; + + auto other = dynamic_cast(check.get()); + + // Check that every time in this one is in the other + for (auto v : coefs) { + if (other->coefs.find(roundTime(v.first)) == other->coefs.end()) { + std::cout << "Can't find Time=" << v.first << std::endl; + ret = false; + } + } + + if (ret) { + std::cout << "Times are the same, now checking parameters at each time" + << std::endl; + for (auto v : coefs) { + auto it = other->coefs.find(v.first); + if (v.second->traj != it->second->traj) ret = false; + if (v.second->rank != it->second->rank) ret = false; + } + } + + if (ret) { + std::cout << "Parameters are the same, now checking coefficients time" + << std::endl; + for (auto v : coefs) { + auto it = other->coefs.find(v.first); + for (int m=0; mtraj; m++) { + for (int n=0; nrank; n++) { + if ((*v.second->coefs)(m, n) != (*it->second->coefs)(m, n)) { + ret = false; + } + } + } + } + } + + return ret; + } + + + Eigen::Tensor + TrajectoryData::getAllCoefs() + { + Eigen::Tensor ret; + + auto times = Times(); + + int traj = coefs.begin()->second->traj; + int rank = coefs.begin()->second->rank; + int ntim = times.size(); + + // Resize the tensor + ret.resize(traj, rank, ntim); + + for (int t=0; tcoefs)(m, n).real(); + } + } + } + + return ret; + } + + std::vector TrajectoryData::makeKeys() + { + std::vector ret; + if (coefs.size()==0) return ret; + + unsigned traj = coefs.begin()->second->traj; + for (unsigned m=0; m& Times, const std::vector>& data, bool verbose) : @@ -2408,6 +2678,14 @@ namespace CoefClasses coefs[roundTime(coef->time)] = p; } + void TrajectoryData::add(CoefStrPtr coef) + { + auto p = std::dynamic_pointer_cast(coef); + if (not p) throw std::runtime_error("TrajectoryData::add: Null coefficient structure, nothing added!"); + + coefs[roundTime(coef->time)] = p; + } + void SphFldCoefs::add(CoefStrPtr coef) { diff --git a/expui/KoopmanRKHS.cc b/expui/KoopmanRKHS.cc index 1e246d1e..6fdf6cf6 100644 --- a/expui/KoopmanRKHS.cc +++ b/expui/KoopmanRKHS.cc @@ -98,6 +98,7 @@ namespace MSSA { // Structure for sorting complex numbers and retrieving a permutation index // using complex_elem = std::pair, int>; + static bool complex_comparator(const complex_elem &lhs, const complex_elem &rhs) { auto a = lhs.first; @@ -123,6 +124,7 @@ namespace MSSA { // Fixed-width matrix element printing // + static void matrix_print(std::ostream& out, const Eigen::MatrixXcd& M) { for (int i=0; i +#include "CoefContainer.H" + +#include + +namespace MSSA +{ + /** + Class for eDMD processing of trajectories using Liouville opertor + RKHS + */ + class LiouvilleRKHS + { + + public: + + //! RKHS types + enum class RKHS + { + Polynomial, + Exponential, + Gaussian + }; + + //! For type reflection and parsing + static std::map RKHS_names; + static std::map RKHS_values; + + protected: + + //@{ + //! Repacked stream data for eDMD + std::map, mSSAkeyCompare > data; + //@} + + //! Coefficient container + CoefContainer coefDB; + + //! Working updated copy of DB + std::shared_ptr newDB; + + //! Parameter database + YAML::Node params; + + //! Primary Koopman/Liouville analysis + void analysis(); + + //! Compute G matrix for RKHS space defined by mu value + Eigen::MatrixXd computeGrammian(double mu, double rat=1.0); + + //! Compute occupation-kernel difference matrix + Eigen::MatrixXd computeGammaDiff(double mu); + + bool computed, reconstructed; + + //@{ + //! Parameters + double mu1=1.0, mu2=1.1, eps=1.0e-4; + bool use_red = true; + //@} + + //! DMD state matrices + Eigen::MatrixXd G1, G2, G3; + + //! Occupation matrix + Eigen::MatrixXd A; + + //! Eigenvectors + Eigen::VectorXcd L; + + //! Eigenfunctions + Eigen::MatrixXcd Xi; + + //! EDMD modes + Eigen::MatrixXcd Phi; + + //! Eigenvalue tolerance + double tol = 1.0e-6; + + //! Max eigenvalue count + int evCount = 100; + + //! Eigenvalues values + Eigen::VectorXd S2, S3; + + //! Eigenvectors + Eigen::MatrixXd Q2, Q3; + + //! Parameters + //@{ + bool verbose, powerf, project; + std::string prefix, config; + int nev; + //@} + + //! Construct YAML node from string + void assignParameters(const std::string pars); + + //! Number of trajectories + int traj; + + //! Number of points in the time series + int numT; + + //! Valid keys for YAML configurations + static const std::set valid_keys; + + //! RKHS parameters + double d, alpha; + + //! RKHS type + RKHS rkhs = RKHS::Polynomial; + + //! RKHS kernels + double kernel(const Eigen::VectorXd& x, + const Eigen::VectorXd& y, + double mu); + + public: + + /** Constructor + + @param spec map/dictionary of tuples listing the Coefs object + and a list of keys + + @param flags is a string of YAML with changes for the default ' + flag values + + @param window is the the window length + @param maxEV is the maximum number of eigenvectors + + The map/dictionary has the following structure: + { + "mnemonic1": (Coefs1, [ [key11], [key12], [...], ...]), + "mnemonic2": (Coefs2, [ [key21], [key22], [...], ...]), + . + . + } + + where the mnemonic is choosen for convenience the set of keys + for each coefficient set, Coefs, specify the indices in the + dimensionaly specific the the Coefs instance itself. + E.g. harmonic and radial indicies for spherical and + cylindrical bases. + */ + LiouvilleRKHS(const mssaConfig& spec, double tol, int count, const std::string flags=""); + + //! Destructor + virtual ~LiouvilleRKHS() {} + + //! Get the eigenvalues + Eigen::VectorXcd eigenvalues() + { + if (not computed) analysis(); + return L; + } + + //! Return the Liouville modes, the coefficients to the eigenfunctions + Eigen::MatrixXcd getModes() + { + if (not computed) analysis(); + return Xi; + } + + //! Evaluate the contribution from the indexed triple + Eigen::VectorXcd modeEval(int index, const Eigen::VectorXd& x); + + //! Return the eigenfunction + std::complex evecEval(int index, const Eigen::VectorXd& x); + + //! Save current MSSA state to an HDF5 file with the given prefix + void saveState(const std::string& prefix); + + //! Restore current MSSA state to an HDF5 file with the given prefix + void restoreState(const std::string& prefix); + + //! Provides a list of all channel keys + std::vector getAllKeys() + { + std::vector ret; + for (auto v : data) ret.push_back(v.first); + return ret; + } + }; + +} +// END namespace MSSA + +#endif diff --git a/expui/LiouvilleRKHS.cc b/expui/LiouvilleRKHS.cc new file mode 100644 index 00000000..8b44dd06 --- /dev/null +++ b/expui/LiouvilleRKHS.cc @@ -0,0 +1,810 @@ +#define TESTING +// +// EDMD (Liouville occupuation kernel version) for EXP coefficients +// +// Uses fixed-rank approximation for the SVD to save time and space. +// Uses the approximate SVD randomized algorithm from Halko, +// Martinsson, and Tropp by default. Use BDCSVD or Jacobi flags for +// the standard methods. Using the randomized method together with +// trajectory matrix rather than covariance matrix analysis may lead +// to large errors in eigenvectors. +// +// The implementation here is based on the +// +// Joel A. Rosenfeld and Rushikesh Kamalapurkar, "Singular Dynamic +// Mode Decomposition", 2023, SIAM J. Appl. Dynamical Systems, +// Vol. 22, No. 3, pp 2357-2381 +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +/* For debugging + #undef eigen_assert + #define eigen_assert(x) \ + if (!(x)) { throw (std::runtime_error("Eigen assert error")); } +*/ + +#include + +#include + +#include +#include +#include +#include +#include + +namespace MSSA { + + // Helper ostream manipulator for debug coefficient key info + // + std::ostream& operator<< (std::ostream& out, const std::vector& t); + + // Get RKHS name from enum + // + std::map LiouvilleRKHS::RKHS_names = + { + {LiouvilleRKHS::RKHS::Polynomial, "Polynomial" }, + {LiouvilleRKHS::RKHS::Exponential, "Exponential"}, + {LiouvilleRKHS::RKHS::Gaussian, "Gaussian" } + }; + + // Get RKHS enum from name + // + std::map LiouvilleRKHS::RKHS_values = + { + {"Polynomial", LiouvilleRKHS::RKHS::Polynomial }, + {"Exponential", LiouvilleRKHS::RKHS::Exponential}, + {"Gaussian", LiouvilleRKHS::RKHS::Gaussian } + }; + + + // RKHS kernel + // + double LiouvilleRKHS::kernel(const Eigen::VectorXd& x, + const Eigen::VectorXd& y, + double mu) + { + double D2 = d*d/(mu*mu); + + if (rkhs == RKHS::Polynomial) { + double prod = x.adjoint()*y; + return pow(1.0 + prod/D2, alpha); + } else if (rkhs == RKHS::Exponential) { + double prod = x.adjoint()*y; + return exp(prod/D2); + } else if (rkhs == RKHS::Gaussian) { + Eigen::VectorXd diff = x - y; + double prod = diff.adjoint()*diff; + return exp(-prod/D2); + } else { + throw std::runtime_error("LiouvilleRKHS: Unknown kernel type"); + } + } + + // Structure for sorting complex numbers and retrieving a permutation index + // + using complex_elem = std::pair, int>; + static + bool complex_comparator(const complex_elem &lhs, const complex_elem &rhs) + { + auto a = lhs.first; + auto b = rhs.first; + + double abs_a = std::abs(a); + double abs_b = std::abs(b); + + // Three step sort: + // (1) By modulus + // (2) By real value + // (3) By imag value + // + if (abs_a == abs_b) { + if (std::real(a) == std::real(b)) + return std::imag(a) < std::imag(b); + else + return std::real(a) < std::real(b); + } else { + return abs_a < abs_b; + } + } + + // Fixed-width matrix element printing + // + static + void matrix_print(std::ostream& out, const Eigen::MatrixXcd& M) + { + for (int i=0; i eigensolver2(G3); + if (eigensolver2.info() != Eigen::Success) { + throw std::runtime_error("LiouvilleRKHS: Eigensolver for Grammian 2 failed"); + } + S3 = eigensolver2.eigenvalues(); + Q3 = eigensolver2.eigenvectors(); + } + + // Compute pseudoinvese of G2 + // + Eigen::MatrixXd G2inv(traj, traj); + Eigen::Vectorxd S2inv(traj); + + for (int n=std::max(0, S2.size()-evCount); n(0, S3.size()-evCount); n eigensolver(PPG); + + // Eigenvalues and eigenvectors of PPG + // + Eigen::VectorXcd lam = eigensolver.eigenvalues(); + Eigen::MatrixXcd V = eigensolver.eigenvectors(); + + // Use equation 7.2 to compute eigenfunctions + // + + + // Use equation 7.3 to compute modes + // + +#ifdef TESTING + std::ofstream file("testing.dat"); + if (file) { + file << std::string(70, '-') << std::endl; + file << "---- Left/right eigenvalues difference" << std::endl; + file << std::string(70, '-') << std::endl; + for (int i=0; i + LiouvilleRKHS::valid_keys = { + "mu1", + "mu2" + "eps", + "d", + "alpha", + "use_red", + "kernel", + "verbose", + "output" + }; + + template + std::string get_shape(const Eigen::EigenBase& x) + { + std::ostringstream oss; + oss << "(" << x.rows() << ", " << x.cols() << ")"; + return oss.str(); + } + + Eigen::VectorXcd LiouvilleRKHS::modeEval(int index, const Eigen::VectorXd& x) + { + if (not computed) analysis(); + + Eigen::VectorXcd ret(nkeys); + ret.setZero(); + + if (index>=0 and index < numT) { + Eigen::VectorXd Y(numT); + for (int j=0; j Psi = Y.transpose()*Q*SP.asDiagonal()*V.col(index); + + ret = Xi.col(index)*Psi; + } + + return ret; + } + + std::complex LiouvilleRKHS::evecEval(int index, const Eigen::VectorXd& x) + { + if (not computed) analysis(); + + static bool first = true; + if (first) { + std::ofstream test("test.data"); + test << "Q" << std::endl << Q << std::endl; + test << "SP" << std::endl << SP << std::endl; + test << "V" << std::endl << V << std::endl; + first = false; + } + + std::complex ret(0.0); + + if (index>=0 and index < numT) { + Eigen::VectorXd Y(numT); + for (int j=0; j()); + + if (params["eps"]) + eps = double(params["eps"].as()); + + if (params["mu1"]) + mu1 = double(params["mu1"].as()); + + if (params["mu2"]) + mu2 = double(params["mu2"].as()); + + if (params["use_red"]) + use_red = bool(params["use_red"].as()); + + if (params["alpha"]) + alpha = double(params["alpha"].as()); + + if (params["kernel"]) { + std::string type = params["kernel"].as(); + if (RKHS_values.find(type) != RKHS_values.end()) + rkhs = RKHS_values[type]; + else + throw std::runtime_error("LiouvilleRKHS: kernel type [" + type + + "] not found"); + } + + if (params["verbose"]) + verbose = bool(params["verbose"]); + + if (params["output"]) prefix = params["output"].as(); + else prefix = "exp_rkhs"; + + } + catch (const YAML::ParserException& e) { + std::cout << "LiouvilleRKHS::assignParameters, parsing error=" << e.what() + << std::endl; + throw; + } + } + + + // Save current KOOPMAN state to an HDF5 file with the given prefix + void LiouvilleRKHS::saveState(const std::string& prefix) + { + if (not computed) return; // No point in saving anything + + if (std::filesystem::exists(prefix + "_rkhs.h5")) { + std::ostringstream sout; + sout << "LiouvilleRKHS::saveState: the file <" << prefix + "_edmd.h5" + << "> already exists.\nPlease delete this file or choose a " + << "different file name"; + throw std::runtime_error(sout.str()); + } + + try { + // Create a new hdf5 file + // + HighFive::File file(prefix + "_rkhs.h5", + HighFive::File::ReadWrite | + HighFive::File::Create); + + // Write the time dimension + // + file.createAttribute("numT", HighFive::DataSpace::From(numT)).write(numT); + + // Write the number of channels + // + file.createAttribute("nKeys", HighFive::DataSpace::From(nkeys)).write(nkeys); + + // Write the number of eigenvalues + // + file.createAttribute("nEV", HighFive::DataSpace::From(nev)).write(nev); + + // Save the key list + // + std::vector keylist; + for (auto k : data) keylist.push_back(k.first); + + // Pad keylist entries to maximum key length for HighFive + // + size_t maxSZ = 0; + for (auto v : keylist) maxSZ = std::max(maxSZ, v.size()); + + // The padding value + // + auto padVal = std::numeric_limits::max(); + for (auto & v : keylist) { + if (v.size() < maxSZ) { + for (auto k=v.size(); k keylist; + h5file.getDataSet("keylist").read(keylist); + + // Remove padded values from K5 store + // + auto padVal = std::numeric_limits::max(); + for (auto & v : keylist) { + std::vector::iterator it; + while ((it = std::find(v.begin(), v.end(), padVal)) != v.end()) + v.erase(it); + } + + // Check key list + // + bool bad = false; + for (int n=0; nfirst.size() != keylist[n].size()) bad = true; + else { + for (int i=0; ifirst[i]) bad = true; + } + } + } + + if (bad) { + std::ostringstream sout; + sout << "LiouvilleRKHS::restoreState: keylist mismatch." << std::endl + << "Can't restore LiouvilleRKHS state! Wanted keylist: "; + for (auto v : data) { + sout << "["; + for (auto u : v.first) sout << u << ' '; + sout << "] "; + } + sout << std::endl << "Found keylist: "; + for (auto v : keylist) { + sout << "["; + for (auto u : v) sout << u << ' '; + sout << "] "; + } + + throw std::runtime_error(sout.str()); + } + + auto analysis = h5file.getGroup("koopmanRKHS_analysis"); + + std::string type = analysis.getDataSet("rkhs").read(); + rkhs = RKHS_values[type]; + + Xi = analysis.getDataSet("Xi" ).read(); + G = analysis.getDataSet("G" ).read(); + A = analysis.getDataSet("A" ).read(); + X = analysis.getDataSet("X" ).read(); + S2 = analysis.getDataSet("S2" ).read(); + SP = analysis.getDataSet("SP" ).read(); + SR = analysis.getDataSet("SR" ).read(); + SL = analysis.getDataSet("SL" ).read(); + Q = analysis.getDataSet("Q" ).read(); + K = analysis.getDataSet("K" ).read(); + U = analysis.getDataSet("U" ).read(); + V = analysis.getDataSet("V" ).read(); + nev = analysis.getDataSet("nev").read(); + + computed = true; + + } catch (HighFive::Exception& err) { + std::cerr << "**** Error opening or reading H5 file ****" << std::endl; + throw; + } + + } + + LiouvilleRKHS::LiouvilleRKHS(const mssaConfig& config, double tol, int count, + const std::string flags) : tol(tol), evCount(count) + { + // Parse the YAML string + // + assignParameters(flags); + + // Eigen OpenMP reporting + // + static bool firstTime = true; + if (firstTime) { + std::cout << "---- Eigen is using " << Eigen::nbThreads() + << " threads" << std::endl; + firstTime = false; + } + + // Now open and parse the coefficient files + // + coefDB = CoefContainer(config, flags); + + numT = coefDB.times.size(); + + // Generate all the channels + // + for (auto key : coefDB.getKeys()) { + data[key] = coefDB.getData(key); + } + + computed = false; + reconstructed = false; + } + // END LiouvilleRKHS constructor + +} +// END namespace MSSA diff --git a/pyEXP/EDMDWrappers.cc b/pyEXP/EDMDWrappers.cc index 88893b79..9840059b 100644 --- a/pyEXP/EDMDWrappers.cc +++ b/pyEXP/EDMDWrappers.cc @@ -4,6 +4,7 @@ #include #include +#include namespace py = pybind11; #include @@ -456,4 +457,165 @@ void EDMDtoolkitClasses(py::module &m) { KoopmanRKHS )"); + py::class_> + h(m, "LiouvilleRKHS"); + + h.def(py::init(), + R"( + Liouville RKHS operator approximatation class + + Parameters + ---------- + config : mssaConfig + the input database of components + tol : double + tolerance for eigenvalue decomposition + count : int + top count of eigenvalues + flag : str + YAML stanza of parameter values + + Returns + ------- + Liouville RKHS instance + + Notes + ----- + The configuration should be in the format: + + {'example': (coefs, keylst, [])} + + where keylst is a list of selected PCs. With a SphericalBasis + for example, the keylst should have the format: + + [[l1, m1, n1], [l2, m2, n2], ...] + + Each sublist represents a PC, where l, m, and n are the + spherical harmonics basis parameters. + )", + py::arg("config"), + py::arg("tol"), + py::arg("count"), + py::arg("flags") = ""); + + h.def("eigenvalues", &LiouvilleRKHS::eigenvalues, + R"( + Vector of eigenvalues from the EDMD RKHS analysis. + + Returns + ------- + numpy.ndarray + the vector of eigenvalues + + Notes + ----- + Note that these eigenvalues are complex. It may be helpful + to separate the magnitude and phase using Numpy's 'absolute' + and 'angle' functions. + )"); + + h.def("saveState", &LiouvilleRKHS::saveState, + R"( + Save current EDMD RKHS state to an HDF5 file with the given prefix + + Parameters + ---------- + prefix : str + output filename prefix + + Returns + ------- + None + )", py::arg("prefix")); + + h.def("restoreState", &LiouvilleRKHS::restoreState, + R"( + Restore current EDMD RKHS state from an HDF5 file + + Parameters + ---------- + prefix : str + input filename prefix + + Notes + ----- + The Liouville RKHS instance must be constructed with the same data and + parameters as the saved state. The restoreState routine will check + for the same data dimension and trend state but can not sure + complete consistency. + )", py::arg("prefix")); + + h.def("getModes", &LiouvilleRKHS::getModes, + R"( + Get the RKHS mode coefficients for all triples + + Returns + ------- + numpy.ndarray + the RKHS mode coefficients + + See also + -------- + Use in conjunction with 'contributions' to visualize the support + from each EDMD RKHS mode to the coefficient series. + )"); + + h.def("modeEval", &LiouvilleRKHS::modeEval, + R"( + Evaluate the contribution from the index triple + + Parameters + ---------- + index : int + the triple index in eigenvalue order + value : ndarray + the input point + + Returns + ------- + numpy.ndarray + the contribution to the trajectory from the indexed triple + + See also + -------- + Use in conjunction with 'contributions' to visualize the support + from each EDMD RKHS mode to the coefficient series. + )", py::arg("index"), py::arg("value")); + + h.def("evecEval", &LiouvilleRKHS::evecEval, + R"( + Evaluate the Liouville eigenfunction with given index + + Parameters + ---------- + index : int + the triple index in eigenvalue order + value : ndarray + the input point + + Returns + ------- + numpy.ndarray + the Liouville eigenfunction + + See also + -------- + Use in conjunction with 'contributions' to visualize the support + from each EDMD RKHS mode to the coefficient series. + )", py::arg("index"), py::arg("value")); + + h.def("getAllKeys", &LiouvilleRKHS::getAllKeys, + R"( + Provides a list of all internal channel keys (for reference) + + Returns + ------- + list(Key) + list of keys in the format described in config file + + See also + -------- + LiouvilleRKHS + )"); + } From 2e0a44be700cee92a934dfc951db9f4e9d2912cc Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 26 Aug 2024 12:24:46 -0400 Subject: [PATCH 12/80] Implemented and compiled, but almost certainly not working. Now for detailed testing [no ci] --- expui/CoefContainer.H | 24 ++- expui/CoefContainer.cc | 85 +++++++++++ expui/LiouvilleRKHS.H | 19 ++- expui/LiouvilleRKHS.cc | 332 ++++++++++++++++++++++------------------- pyEXP/EDMDWrappers.cc | 23 ++- 5 files changed, 325 insertions(+), 158 deletions(-) diff --git a/expui/CoefContainer.H b/expui/CoefContainer.H index 0c8bf39d..fe10b64f 100644 --- a/expui/CoefContainer.H +++ b/expui/CoefContainer.H @@ -129,6 +129,13 @@ namespace MSSA void pack_table(); void unpack_table(); //@} + + //@{ + //! Trajectories + void pack_traj(); + void unpack_traj(); + //@} + //@} //@{ @@ -168,6 +175,8 @@ namespace MSSA //! Copy data from backround back to the coefficient database void background(); + //! Access to coefficients + CoefClasses::CoefsPtr getCoefs() { return coefs; } }; /** @@ -287,7 +296,7 @@ components: std::cout << " ("; for (auto l : key) std::cout << l << "|"; std::cout << ")" << std::endl; - throw std::runtime_error("CoefDB::getData: desired key is not data"); + throw std::runtime_error("CoefContainer::getData: desired key is not data"); } return comps[c]->data[key]; } @@ -322,7 +331,7 @@ components: auto it = namemap.find(name); if (it == namemap.end()) { throw std::runtime_error - ("CoefDB::isComplex: cannot find component with given name"); + ("CoefContainer::isComplex: cannot find component with given name"); } return comps[it->second]->complexKey; @@ -344,6 +353,17 @@ components: return ret; } + //! Access to coefficients + CoefClasses::CoefsPtr getCoefs(const std::string& name) { + auto it = namemap.find(name); + if (it == namemap.end()) { + throw std::runtime_error + ("CoefContainer::getCoefs: cannot find component with given name"); + } + + return comps[it->second]->coefs; + } + }; // END CoefContainer diff --git a/expui/CoefContainer.cc b/expui/CoefContainer.cc index d5deb13d..c1133808 100644 --- a/expui/CoefContainer.cc +++ b/expui/CoefContainer.cc @@ -107,6 +107,8 @@ namespace MSSA pack_cube(); else if (dynamic_cast(coefs.get())) pack_table(); + else if (dynamic_cast(coefs.get())) + pack_traj(); else if (dynamic_cast(coefs.get())) pack_sphfld(); else if (dynamic_cast(coefs.get())) @@ -788,6 +790,89 @@ namespace MSSA } + void CoefDB::pack_traj() + { + auto cur = dynamic_cast(coefs.get()); + + times = cur->Times(); + complexKey = false; + + auto cf = dynamic_cast + ( cur->getCoefStruct(times[0]).get() ); + + int traj = cf->traj; + int rank = cf->rank; + int ntimes = times.size(); + + // Promote desired keys which are the data columns + // + keys.clear(); + for (auto v : keys0) { + if (v.size() != 1) { + std::ostringstream sout; + sout << "CoefDB::pack_traj: key vector should have rank 1; " + << "found rank " << v.size() << " instead"; + throw std::runtime_error(sout.str()); + } + else if (v[0] >= traj) { + std::ostringstream sout; + sout << "CoefDB::pack_traj: requested key=" << v[0] + << " exceeded the number of trajectories, " << traj; + throw std::runtime_error(sout.str()); + } + else keys.push_back(v); // OKAY + } + + // No bkeys for a trajectory + // + bkeys.clear(); + + for (unsigned m=0; m + ( cur->getCoefStruct(times[t]).get() ); + + for (int n=0; ncoefs)(m, n).real(); + } + + } + } + } + + void CoefDB::unpack_traj() + { + auto cf = dynamic_cast + ( coefs->getCoefStruct(times[0]).get() ); + + int traj = cf->traj; + int rank = cf->rank; + int ntimes = times.size(); + + for (int t=0; t + ( coefs->getCoefStruct(times[t]).get() ); + + for (unsigned m=0xc0a57a; mcoefs)(m, n) = data[key][t*rank+n]; + } + // End field loop + } + // END time loop + + } + + CoefContainer::CoefContainer(const CoefContainer& p) { // Protected data diff --git a/expui/LiouvilleRKHS.H b/expui/LiouvilleRKHS.H index 8111af66..423a5bc9 100644 --- a/expui/LiouvilleRKHS.H +++ b/expui/LiouvilleRKHS.H @@ -33,7 +33,7 @@ namespace MSSA //@{ //! Repacked stream data for eDMD - std::map, mSSAkeyCompare > data; + std::map data; //@} //! Coefficient container @@ -54,6 +54,9 @@ namespace MSSA //! Compute occupation-kernel difference matrix Eigen::MatrixXd computeGammaDiff(double mu); + //! Compute occupation-kernel vector + Eigen::VectorXd computeGamma(const Eigen::VectorXd& x, double mu); + bool computed, reconstructed; //@{ @@ -68,6 +71,9 @@ namespace MSSA //! Occupation matrix Eigen::MatrixXd A; + //! Normalized eigenbasis + Eigen::MatrixXcd Vbar; + //! Eigenvectors Eigen::VectorXcd L; @@ -102,6 +108,12 @@ namespace MSSA //! Number of trajectories int traj; + //! Number of active trajectories + int nkeys; + + //! Phase-space rank + int rank; + //! Number of points in the time series int numT; @@ -166,7 +178,10 @@ namespace MSSA } //! Evaluate the contribution from the indexed triple - Eigen::VectorXcd modeEval(int index, const Eigen::VectorXd& x); + Eigen::VectorXcd modeEval(const Eigen::VectorXd& x); + + //! Evaluate the eigenbasis + Eigen::MatrixXcd basisEval(); //! Return the eigenfunction std::complex evecEval(int index, const Eigen::VectorXd& x); diff --git a/expui/LiouvilleRKHS.cc b/expui/LiouvilleRKHS.cc index 8b44dd06..0359ed64 100644 --- a/expui/LiouvilleRKHS.cc +++ b/expui/LiouvilleRKHS.cc @@ -201,9 +201,9 @@ namespace MSSA { if (t2 == 4) f2 = 2; } - auto x1 = data[t1].row(keys[i])*rat; - auto x2 = data[t2].row(keys[j])*rat; - inner += kernel(data[t1].row(keys[i])*rat, data[t2].row(keys[j])*rat, mu) * f2; + auto x1 = data[keys[i]].row(t1)*rat; + auto x2 = data[keys[j]].row(t2)*rat; + inner += kernel(x1, x2, mu) * f2; } G(i, j) += inner*f1; @@ -253,8 +253,8 @@ namespace MSSA { if (t == 4) f = 2; } - sum0 += kernel(data[0 ].row(keys[i]), data[t].row(keys[j]), mu) * f; - sumT += kernel(data[nt-1].row(keys[i]), data[t].row(keys[j]), mu) * f; + sum0 += kernel(data[keys[i]].row(0 ), data[keys[j]].row(t), mu) * f; + sumT += kernel(data[keys[i]].row(nt-1), data[keys[j]].row(t), mu) * f; } A(i, j) += sumT - sum0; @@ -269,6 +269,53 @@ namespace MSSA { } + // Compute gamma vector for RKHS space defined by mu value + Eigen::VectorXd LiouvilleRKHS::computeGamma + (const Eigen::VectorXd& x, double mu) + { + // Use Simpson's rule quadrature + + // Number of trajectories + // + auto keys = getAllKeys(); + traj = keys.size(); + + // Check for odd number of times for Simpson's 1/3 rule + // + int nt = numT; + if (nt/2*2 == nt) nt -= 1; + + // Allocate return vector and set to zero + // + Eigen::VectorXd g(traj); + int f; + + for (int i=0; i(0, S2.size()-evCount); n(0, S3.size()-evCount); n=0 and index < numT) { - Eigen::VectorXd Y(numT); - for (int j=0; j Psi = Y.transpose()*Q*SP.asDiagonal()*V.col(index); - - ret = Xi.col(index)*Psi; - } - - return ret; + return Vbar.transpose()*computeGamma(x, mu1); } - std::complex LiouvilleRKHS::evecEval(int index, const Eigen::VectorXd& x) + Eigen::MatrixXcd LiouvilleRKHS::basisEval() { if (not computed) analysis(); - static bool first = true; - if (first) { - std::ofstream test("test.data"); - test << "Q" << std::endl << Q << std::endl; - test << "SP" << std::endl << SP << std::endl; - test << "V" << std::endl << V << std::endl; - first = false; - } + Eigen::MatrixXcd ret(traj, traj); - std::complex ret(0.0); - - if (index>=0 and index < numT) { - Eigen::VectorXd Y(numT); - for (int j=0; j("nEV", HighFive::DataSpace::From(nev)).write(nev); + // Hilbert space domain parameter + // + file.createAttribute("mu1", HighFive::DataSpace::From(mu1)).write(mu1); + + // Hilbert space range parameter + // + file.createAttribute("mu2", HighFive::DataSpace::From(mu2)).write(mu2); + + // Regularization parameter + // + file.createAttribute("eps", HighFive::DataSpace::From(eps)).write(eps); + // Save the key list // std::vector keylist; @@ -640,19 +606,19 @@ namespace MSSA { HighFive::Group analysis = file.createGroup("koopmanRKHS_analysis"); analysis.createDataSet("rkhs", RKHS_names[rkhs]); - analysis.createDataSet("Xi", Xi ); - analysis.createDataSet("G", G ); - analysis.createDataSet("A", A ); - analysis.createDataSet("X", X ); - analysis.createDataSet("S2", S2 ); - analysis.createDataSet("SP", SP ); - analysis.createDataSet("SR", SR ); - analysis.createDataSet("SL", SL ); - analysis.createDataSet("Q", Q ); - analysis.createDataSet("K", K ); - analysis.createDataSet("U", U ); - analysis.createDataSet("V", V ); - analysis.createDataSet("nEV", nev); + analysis.createDataSet("G1", G1 ); + analysis.createDataSet("G2", G2 ); + analysis.createDataSet("G3", G3 ); + analysis.createDataSet("A", A ); + analysis.createDataSet("Vbar", Vbar); + analysis.createDataSet("L", L ); + analysis.createDataSet("Xi", Xi ); + analysis.createDataSet("Phi", Phi ); + analysis.createDataSet("S2", S2 ); + analysis.createDataSet("S3", S3 ); + analysis.createDataSet("Q2", Q2 ); + analysis.createDataSet("Q3", Q3 ); + } catch (HighFive::Exception& err) { std::cerr << err.what() << std::endl; @@ -674,9 +640,14 @@ namespace MSSA { // Read and check parameters // int nTime, nKeys; + double MU1, MU2, EPS; h5file.getAttribute("numT" ).read(nTime); h5file.getAttribute("nKeys").read(nKeys); + h5file.getAttribute("nEV" ).read(nev ); + h5file.getAttribute("mu1" ).read(MU1 ); + h5file.getAttribute("mu2" ).read(MU2 ); + h5file.getAttribute("eps" ).read(EPS ); // Number of channels // @@ -700,6 +671,38 @@ namespace MSSA { throw std::runtime_error(sout.str()); } + if (nKeys != nkeys) { + std::ostringstream sout; + sout << "LiouvilleRKHS::restoreState: saved state has nkeys=" + << nKeys << " but LiouvilleRKHS expects nkeys=" << nkeys + << ".\nCan't restore LiouvilleRKHS state!"; + throw std::runtime_error(sout.str()); + } + + if (fabs(mu1-MU1) > 1.0e-12) { + std::ostringstream sout; + sout << "LiouvilleRKHS::restoreState: saved state has mu1=" + << MU1 << " but LiouvilleRKHS expects mu1=" << mu1 + << ".\nCan't restore LiouvilleRKHS state!"; + throw std::runtime_error(sout.str()); + } + + if (fabs(mu2-MU2) > 1.0e-12) { + std::ostringstream sout; + sout << "LiouvilleRKHS::restoreState: saved state has mu2=" + << MU2 << " but LiouvilleRKHS expects mu2=" << mu2 + << ".\nCan't restore LiouvilleRKHS state!"; + throw std::runtime_error(sout.str()); + } + + if (fabs(eps-EPS) > 1.0e-18) { + std::ostringstream sout; + sout << "LiouvilleRKHS::restoreState: saved state has eps=" + << EPS << " but LiouvilleRKHS expects eps=" << eps + << ".\nCan't restore LiouvilleRKHS state!"; + throw std::runtime_error(sout.str()); + } + std::vector keylist; h5file.getDataSet("keylist").read(keylist); @@ -750,19 +753,18 @@ namespace MSSA { std::string type = analysis.getDataSet("rkhs").read(); rkhs = RKHS_values[type]; - Xi = analysis.getDataSet("Xi" ).read(); - G = analysis.getDataSet("G" ).read(); - A = analysis.getDataSet("A" ).read(); - X = analysis.getDataSet("X" ).read(); - S2 = analysis.getDataSet("S2" ).read(); - SP = analysis.getDataSet("SP" ).read(); - SR = analysis.getDataSet("SR" ).read(); - SL = analysis.getDataSet("SL" ).read(); - Q = analysis.getDataSet("Q" ).read(); - K = analysis.getDataSet("K" ).read(); - U = analysis.getDataSet("U" ).read(); - V = analysis.getDataSet("V" ).read(); - nev = analysis.getDataSet("nev").read(); + G1 = analysis.getDataSet("G1" ).read(); + G2 = analysis.getDataSet("G2" ).read(); + G3 = analysis.getDataSet("G3" ).read(); + A = analysis.getDataSet("A" ).read(); + Vbar = analysis.getDataSet("Vbar").read(); + L = analysis.getDataSet("L" ).read(); + Xi = analysis.getDataSet("Xi" ).read(); + Phi = analysis.getDataSet("Phi" ).read(); + S2 = analysis.getDataSet("S2" ).read(); + S3 = analysis.getDataSet("S3" ).read(); + Q2 = analysis.getDataSet("Q2" ).read(); + Q3 = analysis.getDataSet("Q3" ).read(); computed = true; @@ -793,14 +795,40 @@ namespace MSSA { // coefDB = CoefContainer(config, flags); + // Number of snapshots + // numT = coefDB.times.size(); - // Generate all the channels + // Database names; there should only be one + // + auto names = coefDB.getNames(); + + // Get coefficients for this database + // + auto coefs = coefDB.getCoefs(names[0]); + + // Get the first coefficient set for parameter reflection + // + auto cf = std::dynamic_pointer_cast + (coefs->getCoefStruct(coefDB.times[0])); + + traj = cf->traj; + rank = cf->rank; + + // Generate data store + // + auto keys = coefDB.getKeys(); // For future generalization... + + // Allocate storage // for (auto key : coefDB.getKeys()) { - data[key] = coefDB.getData(key); + data[key].resize(numT, rank); + auto v = coefDB.getData(key); + for (int t=0; t Date: Mon, 26 Aug 2024 22:26:20 -0400 Subject: [PATCH 13/80] Some additional debugging; needs more explicit checking; clearly not yet passing consistency checks [no ci] --- expui/CoefContainer.cc | 4 +- expui/Coefficients.cc | 21 +++++- expui/LiouvilleRKHS.cc | 51 ++++++++++++-- pyEXP/CoefWrappers.cc | 148 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 215 insertions(+), 9 deletions(-) diff --git a/expui/CoefContainer.cc b/expui/CoefContainer.cc index c1133808..19af4117 100644 --- a/expui/CoefContainer.cc +++ b/expui/CoefContainer.cc @@ -829,7 +829,7 @@ namespace MSSA for (unsigned m=0; m ( coefs->getCoefStruct(times[t]).get() ); - for (unsigned m=0xc0a57a; mcoefs)(m, n) = data[key][t*rank+n]; diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index f272d96d..92f24f43 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -1905,14 +1905,29 @@ namespace CoefClasses data(data), Coefs("table", verbose) { times = Times; + int traj = data.size(); + int ntim = data[0].rows(); + int rank = data[0].cols(); + + if (ntim != times.size()) { + std::ostringstream msg; + msg << "TrajectoryData ERROR (runtime) ntim [" << ntim + << "] != times.size() [" << times.size() << "]"; + throw std::runtime_error(msg.str()); + } + for (int i=0; i(); c->time = times[i]; - c->traj = data[i].rows(); - c->rank = data[i].cols(); + c->traj = traj; + c->rank = rank; c->store.resize(c->traj*c->rank); c->coefs = std::make_shared(c->store.data(), c->traj, c->rank); - *c->coefs = data[i]; + for (int m=0; mcoefs)(m, n) = data[m](i, n); + } + } coefs[roundTime(c->time)] = c; } } diff --git a/expui/LiouvilleRKHS.cc b/expui/LiouvilleRKHS.cc index 0359ed64..26c76690 100644 --- a/expui/LiouvilleRKHS.cc +++ b/expui/LiouvilleRKHS.cc @@ -327,7 +327,8 @@ namespace MSSA { // The number of time points // - numT = data[keys[0]].size(); + numT = data[keys[0]].rows(); + rank = data[keys[0]].cols(); // Get Grammian matrices // @@ -341,7 +342,7 @@ namespace MSSA { if (use_red) { RedSVD::RedSymEigen eigensolver1(G2, evCount); S2 = eigensolver1.eigenvalues(); - Q3 = eigensolver1.eigenvectors(); + Q2 = eigensolver1.eigenvectors(); RedSVD::RedSymEigen eigensolver2(G3, evCount); S3 = eigensolver2.eigenvalues(); @@ -415,12 +416,48 @@ namespace MSSA { computed = true; reconstructed = false; + +#ifdef TESTING + std::ofstream fout("LiouvilleTest.dat"); + if (fout) { + auto header = [&fout] (const std::string& label) { + fout << std::string(80, '-') << std::endl + << "---- " << label << std::endl + << std::string(80, '-') << std::endl; + }; + + header("Parameters"); fout << "Traj=" << traj << " Rank=" << rank << " Ntimes=" << numT << std::endl; + header("Grammian 1"); fout << G1 << std::endl; + header("Grammian 2"); fout << G2 << std::endl; + header("Grammian 3"); fout << G3 << std::endl; + header("Occ kernel"); fout << A << std::endl; + header("Grammian 2 SV"); fout << S2 << std::endl; + header("Grammian 3 SV"); fout << S3 << std::endl; + header("PPG"); fout << PPG << std::endl; + header("Lambda"); fout << lam << std::endl; + header("Vbar"); fout << Vbar << std::endl; + header("Is PPG symmetric?"); + { + Eigen::MatrixXd diff = PPG - PPG.transpose(); + double cmin = std::numeric_limits::infinity(); + double cmax = -std::numeric_limits::infinity(); + auto d = diff.data(); + for (int i=0; i LiouvilleRKHS::valid_keys = { "mu1", - "mu2" + "mu2", "eps", "d", "alpha", @@ -438,6 +475,12 @@ namespace MSSA { return oss.str(); } + std::complex LiouvilleRKHS::evecEval + (int index, const Eigen::VectorXd& x) + { + return Vbar.col(index).adjoint() * computeGamma(x, mu1); + } + Eigen::VectorXcd LiouvilleRKHS::modeEval(const Eigen::VectorXd& x) { if (not computed) analysis(); @@ -819,7 +862,7 @@ namespace MSSA { // auto keys = coefDB.getKeys(); // For future generalization... - // Allocate storage + // Allocate storage and reorder coefficients // for (auto key : coefDB.getKeys()) { data[key].resize(numT, rank); diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index b1ef1d91..dc3242df 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -597,6 +597,84 @@ void CoefficientClasses(py::module &m) { }; + class PyTrajectoryData : public TrajectoryData + { + protected: + void readNativeCoefs(const std::string& file, int stride, double tmin, double tmax) override { + PYBIND11_OVERRIDE(void, TrajectoryData, readNativeCoefs, file, stride, tmin, tmax); + } + + std::string getYAML() override { + PYBIND11_OVERRIDE(std::string, TrajectoryData, getYAML,); + } + + void WriteH5Params(HighFive::File& file) override { + PYBIND11_OVERRIDE(void, TrajectoryData, WriteH5Params, file); + } + + unsigned WriteH5Times(HighFive::Group& group, unsigned count) override { + PYBIND11_OVERRIDE(unsigned, TrajectoryData, WriteH5Times, group, count); + } + + public: + // Inherit the constructors + using TrajectoryData::TrajectoryData; + + Eigen::VectorXcd& getData(double time) override { + PYBIND11_OVERRIDE(Eigen::VectorXcd&, TrajectoryData, getData, time); + } + + void setData(double time, const Eigen::VectorXcd& array) override { + PYBIND11_OVERRIDE(void, TrajectoryData, setData, time, array); + } + + std::shared_ptr getCoefStruct(double time) override { + PYBIND11_OVERRIDE(std::shared_ptr, TrajectoryData, getCoefStruct, + time); + } + + std::vector Times() override { + PYBIND11_OVERRIDE(std::vector, TrajectoryData, Times,); + } + + void WriteH5Coefs(const std::string& prefix) override { + PYBIND11_OVERRIDE(void, TrajectoryData, WriteH5Coefs, prefix); + } + + void ExtendH5Coefs(const std::string& prefix) override { + PYBIND11_OVERRIDE(void, TrajectoryData, ExtendH5Coefs, prefix); + } + + Eigen::MatrixXd& Power(int min, int max) override { + PYBIND11_OVERRIDE(Eigen::MatrixXd&, TrajectoryData, Power, min, max); + } + + bool CompareStanzas(std::shared_ptr check) override { + PYBIND11_OVERRIDE(bool, TrajectoryData, CompareStanzas, check); + } + + void clear() override { + PYBIND11_OVERRIDE(void, TrajectoryData, clear,); + } + + void add(CoefStrPtr coef) override { + PYBIND11_OVERRIDE(void, TrajectoryData, add, coef); + } + + std::vector makeKeys(Key k) override { + PYBIND11_OVERRIDE(std::vector, TrajectoryData, makeKeys, k); + } + + std::shared_ptr deepcopy() override { + PYBIND11_OVERRIDE(std::shared_ptr, TrajectoryData, deepcopy,); + } + + void zerodata() override { + PYBIND11_OVERRIDE(void, TrajectoryData, zerodata,); + } + + }; + py::class_, PyCoefStruct> (m, "CoefStruct") .def(py::init<>(), @@ -1701,6 +1779,76 @@ void CoefficientClasses(py::module &m) { R"( Return a 2-dimensional ndarray indexed by column and time + Returns + ------- + numpy.ndarray + 2-dimensional numpy array containing the data table + )"); + + py::class_, PyTrajectoryData, CoefClasses::Coefs> + (m, "TrajectoryData", "Container for trajectory/orbit data") + .def(py::init(), + R"( + Construct a null TrajectoryData object + + Parameters + ---------- + verbose : bool + display verbose information. + + Returns + ------- + TrajectoryData instance + )", py::arg("verbose")=true) + .def(py::init(), + R"( + Construct a TrajectoryData object from a data file + + Parameters + ---------- + type : str + ascii table data file + + Returns + ------- + TrajectoryData instance + )") + .def(py::init(), + R"( + Construct a TrajectoryData object from a data file + + Parameters + ---------- + type : str + ascii table data file + verbose : bool + display verbose information. + + Returns + ------- + TrajectoryData instance + )", py::arg("filename"), py::arg("verbose")=true) + .def(py::init&, std::vector&, bool>(), + R"( + Construct a TrajectoryData object from data arrays + + Parameters + ---------- + time : ndarray + time data + array : ndarray + data columns + verbose : bool + display verbose information. + + Returns + ------- + TrajectoryData instance + )", py::arg("time"), py::arg("array"), py::arg("verbose")=true) + .def("getAllCoefs", &CoefClasses::TrajectoryData::getAllCoefs, + R"( + Return a 2-dimensional ndarray indexed by column and time + Returns ------- numpy.ndarray From 0b40544047ac1c1070e938f496c71e5b17ab68fa Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Wed, 28 Aug 2024 20:35:34 -0400 Subject: [PATCH 14/80] More updates for Liouville RKHS [no ci] --- expui/LiouvilleRKHS.H | 30 +++--- expui/LiouvilleRKHS.cc | 236 ++++++++++++++++++++++++++--------------- pyEXP/EDMDWrappers.cc | 47 +------- 3 files changed, 169 insertions(+), 144 deletions(-) diff --git a/expui/LiouvilleRKHS.H b/expui/LiouvilleRKHS.H index 423a5bc9..791d4b46 100644 --- a/expui/LiouvilleRKHS.H +++ b/expui/LiouvilleRKHS.H @@ -68,17 +68,26 @@ namespace MSSA //! DMD state matrices Eigen::MatrixXd G1, G2, G3; - //! Occupation matrix + //! Occupation RKHS matrix Eigen::MatrixXd A; + //! Occupation kernel matrix + Eigen::MatrixXd O; + + //! Liouville modes + Eigen::MatrixXcd Xi; + + //! Eigenbasis + Eigen::MatrixXcd V; + //! Normalized eigenbasis Eigen::MatrixXcd Vbar; //! Eigenvectors Eigen::VectorXcd L; - //! Eigenfunctions - Eigen::MatrixXcd Xi; + //! For projection of initial state into eigenfunctions + Eigen::MatrixXcd Ginv; //! EDMD modes Eigen::MatrixXcd Phi; @@ -131,6 +140,9 @@ namespace MSSA const Eigen::VectorXd& y, double mu); + //! Occupation kernel matrix + Eigen::MatrixXd occupation(); + public: /** Constructor @@ -171,20 +183,14 @@ namespace MSSA } //! Return the Liouville modes, the coefficients to the eigenfunctions - Eigen::MatrixXcd getModes() + Eigen::MatrixXcd modeEval() { if (not computed) analysis(); return Xi; } - //! Evaluate the contribution from the indexed triple - Eigen::VectorXcd modeEval(const Eigen::VectorXd& x); - - //! Evaluate the eigenbasis - Eigen::MatrixXcd basisEval(); - - //! Return the eigenfunction - std::complex evecEval(int index, const Eigen::VectorXd& x); + //! Return the eigenfunctions + Eigen::VectorXcd evecEval(const Eigen::VectorXd& x); //! Save current MSSA state to an HDF5 file with the given prefix void saveState(const std::string& prefix); diff --git a/expui/LiouvilleRKHS.cc b/expui/LiouvilleRKHS.cc index 26c76690..634c4b02 100644 --- a/expui/LiouvilleRKHS.cc +++ b/expui/LiouvilleRKHS.cc @@ -167,17 +167,19 @@ namespace MSSA { // Number of trajectories // auto keys = getAllKeys(); - traj = keys.size(); + + if (keys.size() != traj) + throw std::runtime_error("LiouvilleRKHS::computeGrammian: " + "number of trajectories does not match"); // Check for odd number of times for Simpson's 1/3 rule // - int nt = numT; - if (nt/2*2 == nt) nt -= 1; + int nt = numT, f1, f2; + if (nt % 2 == 0) nt -= 1; // Allocate Grammian matrix and set to zero // Eigen::MatrixXd G(traj, traj); - int f1, f2; for (int i=0; i eigensolver1(G2, evCount); + RedSVD::RedSymEigen eigensolver1(G2 + R, evCount); S2 = eigensolver1.eigenvalues(); Q2 = eigensolver1.eigenvectors(); - RedSVD::RedSymEigen eigensolver2(G3, evCount); + RedSVD::RedSymEigen eigensolver2(G3 + R, evCount); S3 = eigensolver2.eigenvalues(); Q3 = eigensolver2.eigenvectors(); } else { - Eigen::SelfAdjointEigenSolver eigensolver1(G2); + Eigen::SelfAdjointEigenSolver eigensolver1(G2 + R); if (eigensolver1.info() != Eigen::Success) { throw std::runtime_error("LiouvilleRKHS: Eigensolver for Grammian 2 failed"); } S2 = eigensolver1.eigenvalues(); Q2 = eigensolver1.eigenvectors(); - Eigen::SelfAdjointEigenSolver eigensolver2(G3); + Eigen::SelfAdjointEigenSolver eigensolver2(G3 + R); if (eigensolver2.info() != Eigen::Success) { throw std::runtime_error("LiouvilleRKHS: Eigensolver for Grammian 2 failed"); } @@ -393,8 +430,8 @@ namespace MSSA { } } - Eigen::MatrixXd PPG = (Q2.transpose()*S2inv.asDiagonal()*Q2) *G1 * - (Q3.transpose()*S3inv.asDiagonal()*Q3) * A; + Eigen::MatrixXd PPG = (Q3*S3inv.asDiagonal()*Q3.transpose()) * G1 * + (Q2*S2inv.asDiagonal()*Q2.transpose()) * A; // Perform eigenanalysis for PPG @@ -403,17 +440,25 @@ namespace MSSA { // Eigenvalues and eigenvectors of PPG // - Eigen::VectorXcd lam = eigensolver.eigenvalues(); - Eigen::MatrixXcd V = eigensolver.eigenvectors(); + L = eigensolver.eigenvalues(); + V = eigensolver.eigenvectors(); // Compute the normalized eigenbasis // - Eigen::MatrixXcd Vbar(V); + Vbar = V; for (int i=0; i::infinity(); double cmax = -std::numeric_limits::infinity(); auto d = diff.data(); @@ -447,7 +481,59 @@ namespace MSSA { cmax = std::max(cmax, d[i]); } fout << "Min: " << cmin << " Max: " << cmax << std::endl; - } + if (fabs(cmin) < 1e-10 and fabs(cmax) < 1e-10) + fout << "SYMMETRIC" << std::endl; + else + fout << "NOT symmetric" << std::endl; + }; + + auto zerocheck = [&fout](const Eigen::MatrixXcd& M) + { + double maxval = 0.0; + for (int i=0; i LiouvilleRKHS::evecEval - (int index, const Eigen::VectorXd& x) - { - return Vbar.col(index).adjoint() * computeGamma(x, mu1); - } - - Eigen::VectorXcd LiouvilleRKHS::modeEval(const Eigen::VectorXd& x) - { - if (not computed) analysis(); - - return Vbar.transpose()*computeGamma(x, mu1); - } - - Eigen::MatrixXcd LiouvilleRKHS::basisEval() + Eigen::VectorXcd LiouvilleRKHS::evecEval(const Eigen::VectorXd& x) { if (not computed) analysis(); - - Eigen::MatrixXcd ret(traj, traj); - - for (unsigned m=0; m Date: Thu, 29 Aug 2024 17:12:55 -0400 Subject: [PATCH 15/80] Added Singular Liouville method to the Liouville RKHS class [no ci] --- expui/LiouvilleRKHS.H | 82 +++++++- expui/LiouvilleRKHS.cc | 450 +++++++++++++++++++++++++++++++++++++---- pyEXP/EDMDWrappers.cc | 20 ++ 3 files changed, 503 insertions(+), 49 deletions(-) diff --git a/expui/LiouvilleRKHS.H b/expui/LiouvilleRKHS.H index 791d4b46..d4b1a9f9 100644 --- a/expui/LiouvilleRKHS.H +++ b/expui/LiouvilleRKHS.H @@ -25,10 +25,21 @@ namespace MSSA Gaussian }; - //! For type reflection and parsing + //! Analysis method + enum class Method + { + Singular, + Eigenfunction + }; + + //! For RKHS type reflection and parsing static std::map RKHS_names; static std::map RKHS_values; + //! For method type reflection and parsing + static std::map Method_names; + static std::map Method_values; + protected: //@{ @@ -45,8 +56,28 @@ namespace MSSA //! Parameter database YAML::Node params; - //! Primary Koopman/Liouville analysis - void analysis(); + //! Method + Method method = Method::Eigenfunction; + + //! Primary Liouville analysis + void analysis() + { + switch (method) + { + case Method::Singular: + singular_analysis(); + break; + case Method::Eigenfunction: + eigenfunction_analysis(); + break; + } + } + + //! Singular Louville analysis + void singular_analysis(); + + //! Eigenfunction Liouville analysis + void eigenfunction_analysis(); //! Compute G matrix for RKHS space defined by mu value Eigen::MatrixXd computeGrammian(double mu, double rat=1.0); @@ -57,6 +88,12 @@ namespace MSSA //! Compute occupation-kernel vector Eigen::VectorXd computeGamma(const Eigen::VectorXd& x, double mu); + //! Compute singular Liouville RKHS matrix + Eigen::MatrixXd computeGramGamma(double mu); + + //! Compute singular Liouville trajectory matrix + Eigen::MatrixXd trajectory(); + bool computed, reconstructed; //@{ @@ -74,9 +111,12 @@ namespace MSSA //! Occupation kernel matrix Eigen::MatrixXd O; - //! Liouville modes + //! Liouville modes for eigenfunction method Eigen::MatrixXcd Xi; + //! Liouville modes for singular method + Eigen::MatrixXd Xh; + //! Eigenbasis Eigen::MatrixXcd V; @@ -99,10 +139,16 @@ namespace MSSA int evCount = 100; //! Eigenvalues values - Eigen::VectorXd S2, S3; + Eigen::VectorXd S1, S2, S3; //! Eigenvectors - Eigen::MatrixXd Q2, Q3; + Eigen::MatrixXd Q1, Q2, Q3; + + //@{ + //! SVD vectors and normalizations + Eigen::MatrixXd UU, VV, V0, Vt; + Eigen::VectorXd SS, Dq, Dp; + //@} //! Parameters //@{ @@ -179,13 +225,20 @@ namespace MSSA Eigen::VectorXcd eigenvalues() { if (not computed) analysis(); - return L; + if (method == Method::Eigenfunction) + return L; + else + return Eigen::VectorXcd(SS); } //! Return the Liouville modes, the coefficients to the eigenfunctions Eigen::MatrixXcd modeEval() { + if (method != Method::Eigenfunction) + throw std::runtime_error("LiouvilleRKHS::modeEval is only for the Eigenfunction method"); + if (not computed) analysis(); + return Xi; } @@ -205,6 +258,21 @@ namespace MSSA for (auto v : data) ret.push_back(v.first); return ret; } + + //! Switch method: Singular or Eigenfunction + void setMethod(const std::string& method_name) + { + auto it = Method_values.find(method_name); + if (it != Method_values.end()) method = it->second; + else throw std::runtime_error("LiouvilleRKHS::setMethod: unknown method <" + method_name + ">. Expecting or "); + } + + //! Flow vector evaluated at a point + Eigen::VectorXd flow(const Eigen::VectorXd& x); + + //! Compute trajectory for an initial state using RK4 (for singular method only) + Eigen::VectorXd computeTrajectory(const Eigen::VectorXd& x); + }; } diff --git a/expui/LiouvilleRKHS.cc b/expui/LiouvilleRKHS.cc index 634c4b02..6a668d3f 100644 --- a/expui/LiouvilleRKHS.cc +++ b/expui/LiouvilleRKHS.cc @@ -75,6 +75,23 @@ namespace MSSA { }; + // Get Method name from enum + // + std::map LiouvilleRKHS::Method_names = + { + {LiouvilleRKHS::Method::Singular, "Singular" }, + {LiouvilleRKHS::Method::Eigenfunction, "Eigenfunction"} + }; + + // Get RKHS enum from name + // + std::map LiouvilleRKHS::Method_values = + { + {"Singular", LiouvilleRKHS::Method::Singular }, + {"Eigenfunction", LiouvilleRKHS::Method::Eigenfunction} + }; + + // RKHS kernel // double LiouvilleRKHS::kernel(const Eigen::VectorXd& x, @@ -265,6 +282,37 @@ namespace MSSA { return A * SF; } + // Compute G matrix for RKHS space defined by mu value + Eigen::MatrixXd LiouvilleRKHS::computeGramGamma(double mu) + { + // Number of trajectories + // + auto keys = getAllKeys(); + if (traj != keys.size()) + throw std::runtime_error("LiouvilleRKHS::computeGammaDiff: " + "number of trajectories does not match"); + + // Allocate Grammian matrix and set to zero + // + Eigen::MatrixXd A(traj, traj); + + int lT = numT - 1; + + for (int i=0; i eigensolver1(G1 + R, evCount); + S1 = eigensolver1.eigenvalues(); + Q1 = eigensolver1.eigenvectors(); + + RedSVD::RedSymEigen eigensolver2(G2 + R, evCount); + S2 = eigensolver2.eigenvalues(); + Q2 = eigensolver2.eigenvectors(); + } else { + Eigen::SelfAdjointEigenSolver eigensolver1(G1 + R); + if (eigensolver1.info() != Eigen::Success) { + throw std::runtime_error("LiouvilleRKHS: Eigensolver for Grammian 1 failed"); + } + S1 = eigensolver1.eigenvalues(); + Q1 = eigensolver1.eigenvectors(); + + Eigen::SelfAdjointEigenSolver eigensolver2(G2 + R); + if (eigensolver2.info() != Eigen::Success) { + throw std::runtime_error("LiouvilleRKHS: Eigensolver for Grammian 2 failed"); + } + S2 = eigensolver2.eigenvalues(); + Q2 = eigensolver2.eigenvectors(); + } + + // Compute the normalized bases + // + Eigen::VectorXd D(traj), Dt(traj); + + for (int i=0; i svd(PP, evCount); + UU = svd.matrixU(); + SS = svd.singularValues(); + VV = svd.matrixV(); + } else { + Eigen::BDCSVD + svd(PP, Eigen::ComputeThinU | Eigen::ComputeThinV); + if (svd.info() != Eigen::Success) { + throw std::runtime_error("LiouvilleRKHS::singular_compute: SVD failure"); + } + UU = svd.matrixU(); + SS = svd.singularValues(); + VV = svd.matrixV(); + } + + // Compute the projection of the state into the eigenbasis + // + Dp.resize(traj); + Dq.resize(traj); + + for (int i=0; i::infinity(); + double cmax = -std::numeric_limits::infinity(); + auto d = diff.data(); + for (int i=0; i eigensolver(PPG); + Eigen::EigenSolver eigensolver(PP); - // Eigenvalues and eigenvectors of PPG + // Eigenvalues and eigenvectors of PP // L = eigensolver.eigenvalues(); V = eigensolver.eigenvectors(); @@ -463,7 +713,7 @@ namespace MSSA { reconstructed = false; #ifdef TESTING - std::ofstream fout("LiouvilleTest.dat"); + std::ofstream fout("LiouvilleTest.E"); if (fout) { auto header = [&fout] (const std::string& label) { fout << std::string(80, '-') << std::endl @@ -504,7 +754,9 @@ namespace MSSA { header("Parameters"); fout << "Traj=" << traj << " Rank=" << rank - << " Ntimes=" << numT << std::endl; + << " Ntimes=" << numT + << " method=" << Method_names[method] + << std::endl; header("Grammian 1"); fout << G1 << std::endl; header("Grammian 2"); fout << G2 << std::endl; @@ -512,7 +764,7 @@ namespace MSSA { header("Occ kernel"); fout << A << std::endl; header("Grammian 2 SV"); fout << S2 << std::endl; header("Grammian 3 SV"); fout << S3 << std::endl; - header("PPG"); fout << PPG << std::endl; + header("PP"); fout << PP << std::endl; header("Lambda"); fout << L << std::endl; header("V"); fout << V << std::endl; header("Vbar"); fout << Vbar << std::endl; @@ -530,16 +782,62 @@ namespace MSSA { header("Is G2 symmetric? Expect YES" ); symcheck(G2 ); header("Is G3 symmetric? Expect YES" ); symcheck(G3 ); header("Is A symmetric? Expect NO" ); symcheck(A ); - header("Is PPG symmetric? Expect NO"); symcheck(PPG); + header("Is PP symmetric? Expect NO" ); symcheck(PP ); header("Good eigenanalysis? Expect YES"); - zerocheck(V*L.asDiagonal()*V.inverse() - PPG); + zerocheck(V*L.asDiagonal()*V.inverse() - PP); } else { std::cerr << "LiouvilleRKHS: Failed to open output file" << std::endl; } #endif } + Eigen::VectorXd LiouvilleRKHS::flow(const Eigen::VectorXd& x) + { + if (method != Method::Singular) + throw std::runtime_error("LiouvilleRKHS::flow is only for the Singular method"); + + Eigen::VectorXd Psi = Dq.asDiagonal() * VV.transpose() * Vt.transpose() * + computeGamma(x, mu1); + + return SS.asDiagonal() * Xh * Psi; + } + + Eigen::VectorXd LiouvilleRKHS::computeTrajectory(const Eigen::VectorXd& x) + { + if (method != Method::Singular) + throw std::runtime_error("LiouvilleRKHS::computeTrajectory is only for the Singular method"); + + //! Define an ODE integrator (RK4) + auto ODE = [this](const Eigen::VectorXd& x, double t, double h) + { + auto k1 = h * flow(x ); + auto k2 = h * flow(x + 0.5*k1); + auto k3 = h * flow(x + 0.5*k2); + auto k4 = h * flow(x + k3 ); + + auto y = x + (k1 + 2.0*k2 + 2.0*k3 + k4)/6.0; + + return std::tuple(y, t + h); + }; + + double dt = coefDB.times[1] - coefDB.times[0]; + + Eigen::MatrixXd ret(numT, rank); + double t = coefDB.times[0]; + + Eigen::VectorXd y = x; + ret.row(0) = y; + + for (int i=1; i LiouvilleRKHS::valid_keys = { "mu1", @@ -549,6 +847,7 @@ namespace MSSA { "alpha", "use_red", "kernel", + "method", "verbose", "output" }; @@ -563,6 +862,9 @@ namespace MSSA { Eigen::VectorXcd LiouvilleRKHS::evecEval(const Eigen::VectorXd& x) { + if (method != Method::Eigenfunction) + throw std::runtime_error("LiouvilleRKHS::evecEval is only for the Eigenfunction method"); + if (not computed) analysis(); return Vbar.transpose() * computeGamma(x, mu1); @@ -624,6 +926,15 @@ namespace MSSA { "] not found"); } + if (params["method"]) { + std::string type = params["method"].as(); + if (Method_values.find(type) != Method_values.end()) + method = Method_values[type]; + else + throw std::runtime_error("LiouvilleRKHS: method type [" + type + + "] not found"); + } + if (params["verbose"]) verbose = bool(params["verbose"]); @@ -683,6 +994,11 @@ namespace MSSA { // file.createAttribute("eps", HighFive::DataSpace::From(eps)).write(eps); + // The algorithm method + // + std::string mthd = Method_names[method]; + file.createAttribute("method", HighFive::DataSpace::From(mthd)).write(mthd); + // Save the key list // std::vector keylist; @@ -710,20 +1026,38 @@ namespace MSSA { // HighFive::Group analysis = file.createGroup("koopmanRKHS_analysis"); - analysis.createDataSet("rkhs", RKHS_names[rkhs]); - analysis.createDataSet("G1", G1 ); - analysis.createDataSet("G2", G2 ); - analysis.createDataSet("G3", G3 ); - analysis.createDataSet("A", A ); - analysis.createDataSet("Vbar", Vbar); - analysis.createDataSet("L", L ); - analysis.createDataSet("Xi", Xi ); - analysis.createDataSet("Phi", Phi ); - analysis.createDataSet("S2", S2 ); - analysis.createDataSet("S3", S3 ); - analysis.createDataSet("Q2", Q2 ); - analysis.createDataSet("Q3", Q3 ); - + if (method == Method::Eigenfunction) { + + analysis.createDataSet("rkhs", RKHS_names[rkhs]); + analysis.createDataSet("G1", G1 ); + analysis.createDataSet("G2", G2 ); + analysis.createDataSet("G3", G3 ); + analysis.createDataSet("A", A ); + analysis.createDataSet("Vbar", Vbar); + analysis.createDataSet("L", L ); + analysis.createDataSet("Xi", Xi ); + analysis.createDataSet("Phi", Phi ); + analysis.createDataSet("S2", S2 ); + analysis.createDataSet("S3", S3 ); + analysis.createDataSet("Q2", Q2 ); + analysis.createDataSet("Q3", Q3 ); + } else { + analysis.createDataSet("rkhs", RKHS_names[rkhs]); + analysis.createDataSet("G1", G1 ); + analysis.createDataSet("G2", G2 ); + analysis.createDataSet("S1", S1 ); + analysis.createDataSet("S2", S2 ); + analysis.createDataSet("Q1", Q1 ); + analysis.createDataSet("Q2", Q2 ); + analysis.createDataSet("V0", V0 ); + analysis.createDataSet("Vt", Vt ); + analysis.createDataSet("U", UU ); + analysis.createDataSet("V", VV ); + analysis.createDataSet("S", SS ); + analysis.createDataSet("Dp", Dp ); + analysis.createDataSet("Dq", Dq ); + analysis.createDataSet("Xh", Xh ); + } } catch (HighFive::Exception& err) { std::cerr << err.what() << std::endl; @@ -746,13 +1080,16 @@ namespace MSSA { // int nTime, nKeys; double MU1, MU2, EPS; + std::string mthd; + + h5file.getAttribute("numT" ).read(nTime); + h5file.getAttribute("nKeys" ).read(nKeys); + h5file.getAttribute("nEV" ).read(nev ); + h5file.getAttribute("mu1" ).read(MU1 ); + h5file.getAttribute("mu2" ).read(MU2 ); + h5file.getAttribute("eps" ).read(EPS ); + h5file.getAttribute("method").read(mthd); - h5file.getAttribute("numT" ).read(nTime); - h5file.getAttribute("nKeys").read(nKeys); - h5file.getAttribute("nEV" ).read(nev ); - h5file.getAttribute("mu1" ).read(MU1 ); - h5file.getAttribute("mu2" ).read(MU2 ); - h5file.getAttribute("eps" ).read(EPS ); // Number of channels // @@ -808,6 +1145,14 @@ namespace MSSA { throw std::runtime_error(sout.str()); } + if (Method_values[mthd] != method) { + std::ostringstream sout; + sout << "LiouvilleRKHS::restoreState: saved state has method=" + << mthd << " but LiouvilleRKHS expects method=" << Method_names[method] + << ".\nCan't restore LiouvilleRKHS state!"; + throw std::runtime_error(sout.str()); + } + std::vector keylist; h5file.getDataSet("keylist").read(keylist); @@ -858,18 +1203,39 @@ namespace MSSA { std::string type = analysis.getDataSet("rkhs").read(); rkhs = RKHS_values[type]; - G1 = analysis.getDataSet("G1" ).read(); - G2 = analysis.getDataSet("G2" ).read(); - G3 = analysis.getDataSet("G3" ).read(); - A = analysis.getDataSet("A" ).read(); - Vbar = analysis.getDataSet("Vbar").read(); - L = analysis.getDataSet("L" ).read(); - Xi = analysis.getDataSet("Xi" ).read(); - Phi = analysis.getDataSet("Phi" ).read(); - S2 = analysis.getDataSet("S2" ).read(); - S3 = analysis.getDataSet("S3" ).read(); - Q2 = analysis.getDataSet("Q2" ).read(); - Q3 = analysis.getDataSet("Q3" ).read(); + if (method == Method::Eigenfunction) { + + G1 = analysis.getDataSet("G1" ).read(); + G2 = analysis.getDataSet("G2" ).read(); + G3 = analysis.getDataSet("G3" ).read(); + A = analysis.getDataSet("A" ).read(); + Vbar = analysis.getDataSet("Vbar").read(); + L = analysis.getDataSet("L" ).read(); + Xi = analysis.getDataSet("Xi" ).read(); + Phi = analysis.getDataSet("Phi" ).read(); + S2 = analysis.getDataSet("S2" ).read(); + S3 = analysis.getDataSet("S3" ).read(); + Q2 = analysis.getDataSet("Q2" ).read(); + Q3 = analysis.getDataSet("Q3" ).read(); + + } else { + + G1 = analysis.getDataSet("G1" ).read(); + G2 = analysis.getDataSet("G2" ).read(); + S1 = analysis.getDataSet("S1" ).read(); + S2 = analysis.getDataSet("S2" ).read(); + Q1 = analysis.getDataSet("Q1" ).read(); + Q2 = analysis.getDataSet("Q2" ).read(); + V0 = analysis.getDataSet("V0" ).read(); + Vt = analysis.getDataSet("Vt" ).read(); + UU = analysis.getDataSet("U" ).read(); + VV = analysis.getDataSet("V" ).read(); + SS = analysis.getDataSet("S" ).read(); + Dp = analysis.getDataSet("Dp" ).read(); + Dq = analysis.getDataSet("Dq" ).read(); + Xh = analysis.getDataSet("Xh" ).read(); + + } computed = true; diff --git a/pyEXP/EDMDWrappers.cc b/pyEXP/EDMDWrappers.cc index 8f17984a..3288f3bb 100644 --- a/pyEXP/EDMDWrappers.cc +++ b/pyEXP/EDMDWrappers.cc @@ -580,6 +580,26 @@ void EDMDtoolkitClasses(py::module &m) { from each EDMD RKHS mode to the coefficient series. )", py::arg("v")); + h.def("computeTrajectory", &LiouvilleRKHS::computeTrajectory, + R"( + Compute the prediction of the trajectory for singular Liouville + + Parameters + ---------- + value : ndarray + the input point + + Returns + ------- + numpy.ndarray + the Liouville eigenfunction + + See also + -------- + Use in conjunction with 'contributions' to visualize the support + from each EDMD RKHS mode to the coefficient series. + )", py::arg("v")); + h.def("getAllKeys", &LiouvilleRKHS::getAllKeys, R"( Provides a list of all internal channel keys (for reference) From 794792b2058d1a9a608472a2dab29149186ee31d Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Fri, 30 Aug 2024 23:20:18 -0400 Subject: [PATCH 16/80] Add OpenMP support to RKHS loops; switch from substep number to substep time step [no ci] --- expui/LiouvilleRKHS.H | 7 +- expui/LiouvilleRKHS.cc | 165 +++++++++++++++++++++++++++++++++-------- pyEXP/EDMDWrappers.cc | 4 +- 3 files changed, 144 insertions(+), 32 deletions(-) diff --git a/expui/LiouvilleRKHS.H b/expui/LiouvilleRKHS.H index d4b1a9f9..e5a4d718 100644 --- a/expui/LiouvilleRKHS.H +++ b/expui/LiouvilleRKHS.H @@ -10,7 +10,10 @@ namespace MSSA { /** Class for eDMD processing of trajectories using Liouville opertor - RKHS + RKHS. This implements both Algorithms 6.1 and 7.1 from the paper + by Joel A. Rosenfeld and Rushikesh Kamalapurkar, "Singular + Dynamic Mode Decomposition", 2023, SIAM J. Appl. Dynamical + Systems, Vol. 22, No. 3, pp 2357-2381. */ class LiouvilleRKHS { @@ -271,7 +274,7 @@ namespace MSSA Eigen::VectorXd flow(const Eigen::VectorXd& x); //! Compute trajectory for an initial state using RK4 (for singular method only) - Eigen::VectorXd computeTrajectory(const Eigen::VectorXd& x); + Eigen::MatrixXd computeTrajectory(const Eigen::VectorXd& x, double h); }; diff --git a/expui/LiouvilleRKHS.cc b/expui/LiouvilleRKHS.cc index 6a668d3f..5def139f 100644 --- a/expui/LiouvilleRKHS.cc +++ b/expui/LiouvilleRKHS.cc @@ -34,6 +34,8 @@ #include #include +#include + /* For debugging #undef eigen_assert #define eigen_assert(x) \ @@ -198,7 +200,39 @@ namespace MSSA { // Eigen::MatrixXd G(traj, traj); +#pragma omp parallel for collapse(2) for (int i=0; iflow(y ); + Eigen::VectorXd k2 = hh * this->flow(y + 0.5*k1); + Eigen::VectorXd k3 = hh * this->flow(y + 0.5*k2); + Eigen::VectorXd k4 = hh * this->flow(y + k3 ); + + y += (k1 + 2.0*k2 + 2.0*k3 + k4)/6.0; + } return std::tuple(y, t + h); }; +#ifdef TESTING + std::cout << "Computing trajectory with " << numT << " X " << rank + << " array" << std::endl; +#endif + + // An array to contain the returned trajectory + Eigen::MatrixXd ret(numT, rank); + + // Compute the time step double dt = coefDB.times[1] - coefDB.times[0]; - Eigen::MatrixXd ret(numT, rank); + // Compute the substep number + int substeps = std::max(1, floor(dt/h)); + + // Initial time double t = coefDB.times[0]; + // Initial state vector Eigen::VectorXd y = x; ret.row(0) = y; + // Integrate the flow for (int i=1; i Date: Tue, 3 Sep 2024 19:35:48 -0400 Subject: [PATCH 17/80] Added OpenMP support [no ci] --- expui/KoopmanRKHS.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/expui/KoopmanRKHS.cc b/expui/KoopmanRKHS.cc index 6fdf6cf6..539e6bfb 100644 --- a/expui/KoopmanRKHS.cc +++ b/expui/KoopmanRKHS.cc @@ -193,6 +193,7 @@ namespace MSSA { // Compute Grammian and action matrices // +#pragma parallel for for (int i=0; i Date: Tue, 3 Sep 2024 19:36:16 -0400 Subject: [PATCH 18/80] Some additional diagnostic checks [no ci] --- expui/LiouvilleRKHS.cc | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/expui/LiouvilleRKHS.cc b/expui/LiouvilleRKHS.cc index 5def139f..aa9b8524 100644 --- a/expui/LiouvilleRKHS.cc +++ b/expui/LiouvilleRKHS.cc @@ -554,21 +554,23 @@ namespace MSSA { // Compute the normalized bases // - Eigen::VectorXd D(traj), Dt(traj); + Eigen::VectorXd D0(traj), Dt(traj), D1(traj), D2(traj); - for (int i=0; i 0.0) D0(i) = 1.0/sqrt(D1(i)); + if (D2(i) > 0.0) Dt(i) = 1.0/sqrt(D2(i)); } - V0 = Q1 * D .asDiagonal(); + V0 = Q1 * D0.asDiagonal(); Vt = Q2 * Dt.asDiagonal(); - Eigen::MatrixXd Gq = Vt.transpose() * G2 * Vt; Eigen::MatrixXd Gp = V0.transpose() * G1 * V0; @@ -663,10 +665,18 @@ namespace MSSA { << " method=" << Method_names[method] << std::endl; - header("Grammian 1"); fout << G1 << std::endl; - header("Grammian 2"); fout << G2 << std::endl; - header("Grammian SV 1"); fout << S1 << std::endl; - header("Grammian SV 2"); fout << S2 << std::endl; + header("G1"); fout << G1 << std::endl; + header("G2"); fout << G2 << std::endl; + header("G1 SV"); fout << S1 << std::endl; + header("G2 SV"); fout << S2 << std::endl; + header("Q1"); fout << Q1 << std::endl; + header("Q2"); fout << Q2 << std::endl; + header("D0"); fout << D0 << std::endl; + header("Dt"); fout << Dt << std::endl; + header("D1"); fout << D1 << std::endl; + header("D2"); fout << D2 << std::endl; + header("V0"); fout << V0 << std::endl; + header("Vt"); fout << Vt << std::endl; header("Traj kernel"); fout << T << std::endl; header("PP"); fout << PP << std::endl; header("S"); fout << SS << std::endl; @@ -1156,8 +1166,6 @@ namespace MSSA { analysis.createDataSet("S2", S2 ); analysis.createDataSet("Q1", Q1 ); analysis.createDataSet("Q2", Q2 ); - analysis.createDataSet("V0", V0 ); - analysis.createDataSet("Vt", Vt ); analysis.createDataSet("U", UU ); analysis.createDataSet("V", VV ); analysis.createDataSet("S", SS ); From e6b81464e658aa9ffae1d5857024f929c7b3cb9f Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Tue, 10 Sep 2024 15:27:56 -0400 Subject: [PATCH 19/80] Update for VTK-9 compile [no ci] --- exputil/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exputil/CMakeLists.txt b/exputil/CMakeLists.txt index 87a25087..786ddfda 100644 --- a/exputil/CMakeLists.txt +++ b/exputil/CMakeLists.txt @@ -52,8 +52,8 @@ set(common_INCLUDE_DIRS $ ${FFTW_INCLUDE_DIRS}) set(common_LINKLIBS ${DEP_LIB} OpenMP::OpenMP_CXX MPI::MPI_CXX - yaml-cpp ${VTK_LIBRARIES} ${HDF5_LIBRARIES} ${HDF5_HL_LIBRARIES} - ${FFTW_DOUBLE_LIB}) + yaml-cpp ${VTK_LIBRARIES} ${HDF5_CXX_LIBRARIES} ${HDF5_LIBRARIES} + ${HDF5_HL_LIBRARIES} ${FFTW_DOUBLE_LIB}) if(ENABLE_CUDA) list(APPEND common_INCLUDE_DIRS ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES} ${CUDAToolkit_INCLUDE_DIRS}) From 56120a0ee574d9e2a95e9e130b27a05351120bd8 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sat, 14 Sep 2024 12:01:57 -0400 Subject: [PATCH 20/80] A bunch of updates for language version consistency based on a Clang 18 compile --- CMakeLists.txt | 9 ++++++++- expui/KoopmanRKHS.cc | 3 ++- exputil/GaussCore.c | 11 +++-------- exputil/GaussCore.h | 4 ++-- exputil/Hermite.c | 12 +++--------- exputil/Hermite.h | 7 ++++--- exputil/Jacobi.c | 15 +++------------ exputil/Jacobi.h | 6 +++--- exputil/Laguerre.c | 8 ++------ exputil/Laguerre.h | 5 +++-- include/DiskWithHalo.H | 6 +++--- include/SLGridMP2.H | 3 +++ 12 files changed, 39 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dbede3fe..75a4ec47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,14 @@ set(CMAKE_CXX_EXTENSIONS OFF) # Compiler flags. Not all tested thoroughly... if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") # using Clang - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + message(STATUS "Clang compiler version is: ${CMAKE_CXX_COMPILER_VERSION}") + if(CMAKE_SYSTEM_NAME MATCHES "Linux") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 18.0.0) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libstdc++") + endif() + endif() elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") # using GCC elseif(CMAKE_CXX_COMPILER_ID MATCHES "Intel") diff --git a/expui/KoopmanRKHS.cc b/expui/KoopmanRKHS.cc index 6fdf6cf6..7c6e1567 100644 --- a/expui/KoopmanRKHS.cc +++ b/expui/KoopmanRKHS.cc @@ -513,7 +513,8 @@ namespace MSSA { Y(j) = kernel(x, X.row(j)); } - ret = Y.transpose()*Q*SP.asDiagonal()*V.col(index); + Eigen::VectorXcd MM = Y.transpose()*Q*SP.asDiagonal(); + ret = MM.dot(V.col(index)); } return ret; diff --git a/exputil/GaussCore.c b/exputil/GaussCore.c index 3d3b6523..cf5c8c75 100644 --- a/exputil/GaussCore.c +++ b/exputil/GaussCore.c @@ -26,14 +26,13 @@ /* Forward declaration for function QQp: */ -static int QQp(); +static int QQp(double, double*, double*); /* Function to test a real value for exceeding -1, and quit on an error if condition not met. */ -void GaussCheck(value) -double value; +void GaussCheck(double value) { if (value <= (-1.0)) { fprintf(stderr, "Gauss package: parameter out of range: %g\n", value); @@ -54,11 +53,7 @@ static int n1; /* The workhorse. */ -void GaussMaster(n, alpha, beta, conflag, abscis, weight) -int n; -double alpha, beta; -int conflag; -double abscis[], weight[]; +void GaussMaster(int n, double alpha, double beta, int conflag, double abscis[], double weight[]) { #define FALSE 0 #define TRUE 1 diff --git a/exputil/GaussCore.h b/exputil/GaussCore.h index d8ef4fc0..eb46e4d5 100644 --- a/exputil/GaussCore.h +++ b/exputil/GaussCore.h @@ -12,8 +12,8 @@ #define CONFLUENT 1 #define NOCONFLUENT 0 -extern void GaussMaster(); -extern void GaussCheck(); +extern void GaussMaster(int, double, double, int, double[], double[]); +extern void GaussCheck(double); /* ARGUMENT LISTS: diff --git a/exputil/Hermite.c b/exputil/Hermite.c index b7ed38b3..d9d19237 100644 --- a/exputil/Hermite.c +++ b/exputil/Hermite.c @@ -23,9 +23,7 @@ */ #define odd(n) ((unsigned)(n) & 01) -void Odd_Hermite(n, alpha, abscis, weight, w0) -int n; -double alpha, abscis[], weight[], *w0; +void Odd_Hermite(int n, double alpha, double abscis[], double weight[], double* w0) { int k; @@ -41,9 +39,7 @@ double alpha, abscis[], weight[], *w0; }; -void Even_Hermite(n, alpha, abscis, weight) -int n; -double alpha, abscis[], weight[]; +void Even_Hermite(int n, double alpha, double abscis[], double weight[]) { int k; @@ -59,9 +55,7 @@ double alpha, abscis[], weight[]; }; -void Hermite(n, alpha, abscis, weight) -int n; -double alpha, abscis[], weight[]; +void Hermite(int n, double alpha, double abscis[], double weight[]) { int n1, k; diff --git a/exputil/Hermite.h b/exputil/Hermite.h index b5ff4937..42c16235 100644 --- a/exputil/Hermite.h +++ b/exputil/Hermite.h @@ -15,9 +15,10 @@ Translation of module Hermite (export). */ -extern void Odd_Hermite(); -extern void Even_Hermite(); -extern void Hermite(); +extern void Odd_Hermite(int n, double alpha, double abscis[], double weight[], double* w0); +extern void Even_Hermite(int n, double alpha, double abscis[], double weight[]); +extern void Hermite(int n, double alpha, double abscis[], double weight[]); + /* ARGUMENT LISTS: diff --git a/exputil/Jacobi.c b/exputil/Jacobi.c index afe4557d..b9febadc 100644 --- a/exputil/Jacobi.c +++ b/exputil/Jacobi.c @@ -20,10 +20,7 @@ #define sqr(x) ((x)*(x)) -void Jacobi(n, alpha, beta, abscis, weight) -int n; -double alpha, beta; -double abscis[], weight[]; +void Jacobi(int n, double alpha, double beta, double abscis[], double weight[]) { int k; @@ -34,10 +31,7 @@ double abscis[], weight[]; } -void Radau_Jacobi(n, alpha, beta, abscis, weight, leftw) -int n; -double alpha, beta; -double abscis[], weight[], *leftw; +void Radau_Jacobi(int n, double alpha, double beta, double abscis[], double weight[], double* leftw) { int k; double temp; @@ -62,10 +56,7 @@ double abscis[], weight[], *leftw; }; -void Lobatto_Jacobi(n, alpha, beta, abscis, weight, leftw, rightw) -int n; -double alpha, beta; -double abscis[], weight[], *leftw, *rightw; +void Lobatto_Jacobi(int n, double alpha, double beta, double abscis[], double weight[], double* leftw, double* rightw) { int k; double temp1, temp2; diff --git a/exputil/Jacobi.h b/exputil/Jacobi.h index d719820c..fbbc14ae 100644 --- a/exputil/Jacobi.h +++ b/exputil/Jacobi.h @@ -16,9 +16,9 @@ */ -extern void Jacobi(); -extern void Radau_Jacobi(); -extern void Lobatto_Jacobi(); +extern void Jacobi(int n, double alpha, double beta, double abscis[], double weight[]); +extern void Radau_Jacobi(int, double alpha, double beta, double abscis[], double weight[], double* leftw); +extern void Lobatto_Jacobi(int n, double alpha, double beta, double abscis[], double weight[], double* leftw, double* rightw); /* ARGUMENT LISTS: void Jacobi(n, alpha, beta, abscis, weight) diff --git a/exputil/Laguerre.c b/exputil/Laguerre.c index 2bc97ab9..834bf185 100644 --- a/exputil/Laguerre.c +++ b/exputil/Laguerre.c @@ -15,18 +15,14 @@ #include "GaussCore.h" #include "Laguerre.h" -void Laguerre(n, alpha, abscis, weight) -int n; -double alpha, abscis[], weight[]; +void Laguerre(int n, double alpha, double abscis[], double weight[]) { GaussCheck(alpha); GaussMaster(n, alpha, 0.0, CONFLUENT, abscis, weight); }; -void Radau_Laguerre(n, alpha, abscis, weight, leftw) -int n; -double alpha, abscis[], weight[], *leftw; +void Radau_Laguerre(int n, double alpha, double abscis[], double weight[], double* leftw) { int k; double temp; diff --git a/exputil/Laguerre.h b/exputil/Laguerre.h index 2afdfc94..62819940 100644 --- a/exputil/Laguerre.h +++ b/exputil/Laguerre.h @@ -15,8 +15,9 @@ Translation of module Laguerre (export). */ -extern void Laguerre(); -extern void Radau_Laguerre(); +extern void Laguerre(int n, double alpha, double abscis[], double weight[]); + +extern void Radau_Laguerre(int n, double alpha, double abscis[], double weight[], double* leftw); /* ARGUMENT LISTS: diff --git a/include/DiskWithHalo.H b/include/DiskWithHalo.H index 79e9038f..8f96ce36 100644 --- a/include/DiskWithHalo.H +++ b/include/DiskWithHalo.H @@ -66,13 +66,13 @@ public: dur = dur1 + dur2; } - double get_mass(const double x1, const double x2, const double x3) + double get_mass(const double x1, const double x2, const double x3) override { return get_mass(sqrt(x1*x1 + x2*x2 + x3*x3)); } - double get_density(const double x1, const double x2, const double x3) + double get_density(const double x1, const double x2, const double x3) override { return get_density(sqrt(x1*x1 + x2*x2 + x3*x3)); } - double get_pot(const double x1, const double x2, const double x3) + double get_pot(const double x1, const double x2, const double x3) override { return get_pot(sqrt(x1*x1 + x2*x2 + x3*x3)); } // Addiional member functions diff --git a/include/SLGridMP2.H b/include/SLGridMP2.H index f83d98fe..e521b90e 100644 --- a/include/SLGridMP2.H +++ b/include/SLGridMP2.H @@ -314,6 +314,9 @@ private: //! Constructor CoordMap(double H) : H(H) {} + //! Destructor + virtual ~CoordMap() {} + //! Convert from vertical to mapped coordinate virtual double z_to_xi (double z) = 0; From 9434abed966b4b96831d6a0d2c0fce3a9f777556 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 19 Sep 2024 13:27:47 -0400 Subject: [PATCH 21/80] Missing cassert --- expui/BiorthBess.H | 1 + 1 file changed, 1 insertion(+) diff --git a/expui/BiorthBess.H b/expui/BiorthBess.H index ed0dcb57..05a2cef4 100644 --- a/expui/BiorthBess.H +++ b/expui/BiorthBess.H @@ -1,6 +1,7 @@ #ifndef _BiorthBess_H_ #define _BiorthBess_H_ +#include #include #include #include From 0b046887f11d3ca363a28f389e9e0565c1478cb1 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 26 Sep 2024 19:55:41 -0400 Subject: [PATCH 22/80] Use Eigen::Ref<> to pass arrays to prevent pybind11 mapping failure in pyEXP --- expui/Coefficients.H | 28 +++++++++++++--------------- expui/Coefficients.cc | 22 +++++++++++----------- pyEXP/CoefWrappers.cc | 14 +++++++------- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/expui/Coefficients.H b/expui/Coefficients.H index e31c37ed..dbfdf99c 100644 --- a/expui/Coefficients.H +++ b/expui/Coefficients.H @@ -111,7 +111,7 @@ namespace CoefClasses virtual Eigen::VectorXcd& getData(double time) = 0; //! Set coefficient store at given time to the provided matrix - virtual void setData(double time, const Eigen::VectorXcd& data) = 0; + virtual void setData(double time, Eigen::Ref data) = 0; //! Interpolate coefficient matrix at given time std::tuple interpolate(double time); @@ -273,10 +273,10 @@ namespace CoefClasses Eigen::MatrixXcd& getMatrix(double time); //! Set coefficient matrix at given time - virtual void setData(double time, const Eigen::VectorXcd& arr); + virtual void setData(double time, Eigen::Ref arr); //! Natural data setter for pyEXP - void setMatrix(double time, const Eigen::MatrixXcd& mat); + void setMatrix(double time, Eigen::Ref mat); //! Interpolate coefficient matrix at given time std::tuple interpolate(double time) @@ -411,10 +411,10 @@ namespace CoefClasses Eigen::MatrixXcd& getMatrix(double time); //! Set coefficient matrix at given time - virtual void setData(double time, const Eigen::VectorXcd& arr); + virtual void setData(double time, Eigen::Ref arr); //! For pyEXP - void setMatrix(double time, const Eigen::MatrixXcd& arr); + void setMatrix(double time, Eigen::Ref arr); //! Interpolate coefficient matrix at given time std::tuple interpolate(double time) @@ -542,7 +542,7 @@ namespace CoefClasses virtual Eigen3d& getTensor(double time); //! Set coefficient matrix at given time - virtual void setData(double time, const Eigen::VectorXcd& dat); + virtual void setData(double time, Eigen::Ref dat); //! Native version virtual void setTensor(double time, const Eigen3d& dat); @@ -673,7 +673,7 @@ namespace CoefClasses virtual Eigen3d& getTensor(double time); //! Set coefficient matrix at given time - virtual void setData(double time, const Eigen::VectorXcd& dat); + virtual void setData(double time, Eigen::Ref dat); //! Native version virtual void setTensor(double time, const Eigen3d& dat); @@ -810,9 +810,10 @@ namespace CoefClasses /** Set coefficient matrix at given time. This is for pybind11, since the operator() will not allow lvalue assignment, it seems. */ - virtual void setData(double time, const Eigen::VectorXcd& arr); + virtual void setData(double time, Eigen::Ref arr); - void setArray(double time, const Eigen::VectorXcd& arr) { setData(time, arr); } + void setArray(double time, Eigen::Ref arr) + { setData(time, arr); } //! Get coefficient structure at a given time virtual std::shared_ptr getCoefStruct(double time) @@ -915,10 +916,7 @@ namespace CoefClasses /** Set coefficient matrix at given time. This is for pybind11, since the operator() will not allow lvalue assignment, it seems. */ - virtual void setData(double time, const Eigen::VectorXcd& arr); - - void setMatrix(double time, const Eigen::MatrixXd& arr) - { setData(time, arr); } + virtual void setData(double time, Eigen::Ref arr); //! Get coefficient structure at a given time virtual std::shared_ptr getCoefStruct(double time) @@ -1020,7 +1018,7 @@ namespace CoefClasses SphFldStruct::coefType& getMatrix(double time); //! Set coefficient matrix at given time - virtual void setData(double time, const Eigen::VectorXcd& arr); + virtual void setData(double time, Eigen::Ref arr); //! Natural data setter for pyEXP void setMatrix(double time, const SphFldStruct::coefType& mat); @@ -1147,7 +1145,7 @@ namespace CoefClasses CylFldStruct::coefType & getMatrix(double time); //! Set coefficient matrix at given time - virtual void setData(double time, const Eigen::VectorXcd& arr); + virtual void setData(double time, Eigen::Ref arr); //! Natural data setter for pyEXP void setMatrix(double time, const CylFldStruct::coefType& mat); diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index 92f24f43..d141554b 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -489,7 +489,7 @@ namespace CoefClasses return mat; } - void SphCoefs::setData(double time, const Eigen::VectorXcd& dat) + void SphCoefs::setData(double time, Eigen::Ref dat) { auto it = coefs.find(roundTime(time)); @@ -504,7 +504,7 @@ namespace CoefClasses } } - void SphCoefs::setMatrix(double time, const Eigen::MatrixXcd& dat) + void SphCoefs::setMatrix(double time, Eigen::Ref dat) { auto it = coefs.find(roundTime(time)); @@ -911,7 +911,7 @@ namespace CoefClasses return mat; } - void CylCoefs::setData(double time, const Eigen::VectorXcd& dat) + void CylCoefs::setData(double time, Eigen::Ref dat) { auto it = coefs.find(roundTime(time)); @@ -926,7 +926,7 @@ namespace CoefClasses } } - void CylCoefs::setMatrix(double time, const Eigen::MatrixXcd& dat) + void CylCoefs::setMatrix(double time, Eigen::Ref dat) { auto it = coefs.find(roundTime(time)); @@ -1267,7 +1267,7 @@ namespace CoefClasses return arr; } - void SlabCoefs::setData(double time, const Eigen::VectorXcd& dat) + void SlabCoefs::setData(double time, Eigen::Ref dat) { auto it = coefs.find(roundTime(time)); @@ -1619,7 +1619,7 @@ namespace CoefClasses return arr; } - void CubeCoefs::setData(double time, const Eigen::VectorXcd& dat) + void CubeCoefs::setData(double time, Eigen::Ref dat) { auto it = coefs.find(roundTime(time)); @@ -2007,7 +2007,7 @@ namespace CoefClasses return arr; } - void TrajectoryData::setData(double time, const Eigen::VectorXcd& dat) + void TrajectoryData::setData(double time, Eigen::Ref dat) { auto it = coefs.find(roundTime(time)); @@ -2253,7 +2253,7 @@ namespace CoefClasses return arr; } - void TableData::setData(double time, const Eigen::VectorXcd& dat) + void TableData::setData(double time, Eigen::Ref dat) { auto it = coefs.find(roundTime(time)); @@ -2751,7 +2751,7 @@ namespace CoefClasses return *mat; } - void SphFldCoefs::setData(double time, const Eigen::VectorXcd& dat) + void SphFldCoefs::setData(double time, Eigen::Ref dat) { auto it = coefs.find(roundTime(time)); @@ -3190,7 +3190,7 @@ namespace CoefClasses return *mat; } - void CylFldCoefs::setData(double time, const Eigen::VectorXcd& dat) + void CylFldCoefs::setData(double time, Eigen::Ref dat) { auto it = coefs.find(roundTime(time)); @@ -3211,7 +3211,7 @@ namespace CoefClasses if (it == coefs.end()) { std::ostringstream str; - str << "CylNfldCoefs::setMatrix: requested time=" << time << " not found"; + str << "CylFldCoefs::setMatrix: requested time=" << time << " not found"; throw std::runtime_error(str.str()); } else { it->second->allocate(); diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index dc3242df..3027933b 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -155,7 +155,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE_PURE(Eigen::VectorXcd&, Coefs, getData, time); } - void setData(double time, const Eigen::VectorXcd& array) override { + void setData(double time, Eigen::Ref array) override { PYBIND11_OVERRIDE_PURE(void, Coefs, setData, time, array); } @@ -231,7 +231,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE(Eigen::VectorXcd&, SphCoefs, getData, time); } - void setData(double time, const Eigen::VectorXcd& array) override { + void setData(double time, Eigen::Ref array) override { PYBIND11_OVERRIDE(void, SphCoefs, setData, time, array); } @@ -310,7 +310,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE(Eigen::VectorXcd&, CylCoefs, getData, time); } - void setData(double time, const Eigen::VectorXcd& array) override { + void setData(double time, Eigen::Ref array) override { PYBIND11_OVERRIDE(void, CylCoefs, setData, time, array); } @@ -388,7 +388,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE(Eigen::VectorXcd&, SlabCoefs, getData, time); } - void setData(double time, const Eigen::VectorXcd& array) override { + void setData(double time, Eigen::Ref array) override { PYBIND11_OVERRIDE(void, SlabCoefs, setData, time, array); } @@ -467,7 +467,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE(Eigen::VectorXcd&, CubeCoefs, getData, time); } - void setData(double time, const Eigen::VectorXcd& array) override { + void setData(double time, Eigen::Ref array) override { PYBIND11_OVERRIDE(void, CubeCoefs, setData, time, array); } @@ -546,7 +546,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE(Eigen::VectorXcd&, TableData, getData, time); } - void setData(double time, const Eigen::VectorXcd& array) override { + void setData(double time, Eigen::Ref array) override { PYBIND11_OVERRIDE(void, TableData, setData, time, array); } @@ -624,7 +624,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE(Eigen::VectorXcd&, TrajectoryData, getData, time); } - void setData(double time, const Eigen::VectorXcd& array) override { + void setData(double time, Eigen::Ref array) override { PYBIND11_OVERRIDE(void, TrajectoryData, setData, time, array); } From d583a10f6ffcff48f2a77a757ddebb5db3a82e16 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 26 Sep 2024 19:56:09 -0400 Subject: [PATCH 23/80] Fix norm issue discovered in C++ tests --- exputil/EmpCyl2d.cc | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/exputil/EmpCyl2d.cc b/exputil/EmpCyl2d.cc index af0901e4..26a5488c 100644 --- a/exputil/EmpCyl2d.cc +++ b/exputil/EmpCyl2d.cc @@ -638,7 +638,7 @@ std::shared_ptr EmpCyl2d::createModel() if (name.find("kuzmin") != std::string::npos) { if (myid==0) { std::cout << "---- EmpCyl2d::ModelCyl: Making a Kuzmin disk"; - if (Params["parameters"]) std::cout << " with " << Params["parameters"]; + if (Params["parameters"]) std::cout << " with" << std::endl << Params["parameters"]; std::cout << std::endl; } return std::make_shared(Params["parameters"]); @@ -647,7 +647,7 @@ std::shared_ptr EmpCyl2d::createModel() if (name.find("mestel") != std::string::npos) { if (myid==0) { std::cout << "---- EmpCyl2d::ModelCyl: Making a finite Mestel disk"; - if (Params["parameters"]) std::cout << " with " << Params["parameters"]; + if (Params["parameters"]) std::cout << " with" << std::endl << Params["parameters"]; std::cout << std::endl; } return std::make_shared(Params["parameters"]); @@ -656,7 +656,9 @@ std::shared_ptr EmpCyl2d::createModel() if (name.find("zang") != std::string::npos) { if (myid==0) { std::cout << "---- EmpCyl2d::ModelCyl: Making a double-tapered Zang"; - if (Params["parameters"]) std::cout << " with " << Params["parameters"]; + YAML::Emitter out; + out << YAML::Flow << Params["parameters"]; + if (Params["parameters"]) std::cout << " with " << out.c_str(); std::cout << std::endl; } return std::make_shared(Params["parameters"]); @@ -665,7 +667,7 @@ std::shared_ptr EmpCyl2d::createModel() if (name.find("expon") != std::string::npos) { if (myid==0) { std::cout << "---- EmpCyl2d::ModelCyl: Making an Exponential disk"; - if (Params["parameters"]) std::cout << " with " << Params["parameters"]; + if (Params["parameters"]) std::cout << " with" << std::endl << Params["parameters"]; std::cout << std::endl; } return std::make_shared(Params["parameters"]); @@ -834,7 +836,7 @@ void EmpCyl2d::create_tables() for (int l=0; ldens(rr) * basis->potl(m, j, rr) * basis->potl(m, l, rr) - / sqrt(basis->norm(j, m)*basis->norm(l, m)); + / sqrt(basis->norm(j, m)*basis->norm(l, m)*4.0); } } } @@ -871,9 +873,9 @@ void EmpCyl2d::create_tables() if (m==0) xgrid[i] = r; for (int n=0; npotl(m, n, r) / sqrt(basis->norm(n, m)); - den(n) = basis->dens(m, n, r) / sqrt(basis->norm(n, m)); - dph(n) = basis->dpot(m, n, r) / sqrt(basis->norm(n, m)); + pot(n) = basis->potl(m, n, r) / sqrt(basis->norm(n, m)*0.5); + den(n) = basis->dens(m, n, r) / sqrt(basis->norm(n, m)*2.0); + dph(n) = basis->dpot(m, n, r) / sqrt(basis->norm(n, m)*0.5); } pot = U.transpose() * pot; @@ -1201,7 +1203,7 @@ double EmpCyl2d::get_potl(double r, int M, int N) checkMN(M, N, "get_potl"); if (basis_test) { - return basis->potl(M, N, r)/sqrt(basis->norm(N, M)); + return basis->potl(M, N, r)/sqrt(basis->norm(N, M)*0.5); } int lo, hi; @@ -1216,7 +1218,7 @@ double EmpCyl2d::get_dens(double r, int M, int N) checkMN(M, N, "get_dens"); if (basis_test) { - return basis->dens(M, N, r)/sqrt(basis->norm(N, M)); + return basis->dens(M, N, r)/sqrt(basis->norm(N, M)*2.0); } int lo, hi; @@ -1231,7 +1233,7 @@ double EmpCyl2d::get_dpot(double r, int M, int N) checkMN(M, N, "get_dpot"); if (basis_test) { - return basis->dpot(M, N, r)/sqrt(basis->norm(N, M)); + return basis->dpot(M, N, r)/sqrt(basis->norm(N, M)*0.5); } int lo, hi; @@ -1305,7 +1307,7 @@ void EmpCyl2d::checkCoefs() for (int j=0; jdens(rr) * get_potl(rr, 0, j); - coef0(j) += fac * disk->dens(rr) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)); + coef0(j) += fac * disk->dens(rr) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)*0.5); } } @@ -1329,8 +1331,8 @@ void EmpCyl2d::checkCoefs() for (int j=0; jpotl(0, j, rr) / sqrt(basis->norm(j, 0)); - zz += coef0(j) * basis->dens(0, j, rr) / sqrt(basis->norm(j, 0)); + yy += coef0(j) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)*0.5); + zz += coef0(j) * basis->dens(0, j, rr) / sqrt(basis->norm(j, 0)*2.0); } std::cout << std::setw(16) << rr From 0fea8e3464a8cfd36142d2f236ee810e12f6ab09 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 26 Sep 2024 19:57:04 -0400 Subject: [PATCH 24/80] Fixes for H5 API --- exputil/ParticleReader.cc | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/exputil/ParticleReader.cc b/exputil/ParticleReader.cc index 99f09a8c..4042b544 100644 --- a/exputil/ParticleReader.cc +++ b/exputil/ParticleReader.cc @@ -334,7 +334,8 @@ namespace PR { const H5std_string GROUP_NAME_what ("/Header"); H5::H5File file( FILE_NAME, H5F_ACC_RDONLY ); - H5::Group what(file.openGroup( GROUP_NAME_what )); + // H5::Group what(file.openGroup( GROUP_NAME_what )); + H5::Group what = file.openGroup( GROUP_NAME_what ); // Get time { @@ -414,7 +415,8 @@ namespace PR { const H5std_string GROUP_NAME_what ("/Header"); H5::H5File file( FILE_NAME, H5F_ACC_RDONLY ); - H5::Group what(file.openGroup( GROUP_NAME_what )); + // H5::Group what(file.openGroup( GROUP_NAME_what )); + H5::Group what = file.openGroup( GROUP_NAME_what ); // Get time { @@ -447,7 +449,8 @@ namespace PR { totalCount = npart[ptype]; std::string grpnam = "/" + sout.str(); - H5::Group grp(file.openGroup(grpnam)); + // H5::Group grp(file.openGroup(grpnam)); + H5::Group grp = file.openGroup(grpnam); H5::DataSet dataset = grp.openDataSet("Coordinates"); H5::DataSpace dataspace = dataset.getSpace(); From 6dad8ce7e1fe7e223e1113d598d405ceeff50751 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 26 Sep 2024 19:57:48 -0400 Subject: [PATCH 25/80] Do not copy data if nimax or ndmax are zero --- src/ParticleFerry.cc | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/ParticleFerry.cc b/src/ParticleFerry.cc index 142ca230..30aa3d7c 100644 --- a/src/ParticleFerry.cc +++ b/src/ParticleFerry.cc @@ -126,13 +126,17 @@ void ParticleFerry::particlePack(PartPtr in, char* buffer) // std::vector iattrib // - memcpy(&buffer[pos], &in->iattrib[0], nimax*sizeof(int)); - pos += sizeof(int)*nimax; + if (nimax) { + memcpy(&buffer[pos], &in->iattrib[0], nimax*sizeof(int)); + pos += sizeof(int)*nimax; + } // std::vector dattrib // - memcpy(&buffer[pos], &in->dattrib[0], ndmax*sizeof(double)); - pos += sizeof(double)*ndmax; + if (ndmax) { + memcpy(&buffer[pos], &in->dattrib[0], ndmax*sizeof(double)); + pos += sizeof(double)*ndmax; + } // unsigned level // @@ -210,15 +214,19 @@ void ParticleFerry::particleUnpack(PartPtr out, char* buffer) // std::vector iattrib // - out->iattrib.resize(nimax); - memcpy(&out->iattrib[0], &buffer[pos], nimax*sizeof(int)); - pos += sizeof(int)*nimax; + if (nimax) { + out->iattrib.resize(nimax); + memcpy(&out->iattrib[0], &buffer[pos], nimax*sizeof(int)); + pos += sizeof(int)*nimax; + } // std::vector dattrib // - out->dattrib.resize(ndmax); - memcpy(&out->dattrib[0], &buffer[pos], ndmax*sizeof(double)); - pos += sizeof(double)*ndmax; + if (ndmax) { + out->dattrib.resize(ndmax); + memcpy(&out->dattrib[0], &buffer[pos], ndmax*sizeof(double)); + pos += sizeof(double)*ndmax; + } // unsigned level // From 01ee76b4dcfb63b86dde4f17f480ac6f1760e71c Mon Sep 17 00:00:00 2001 From: mdw Date: Thu, 26 Sep 2024 20:00:12 -0400 Subject: [PATCH 26/80] Missing cassert header --- expui/BiorthBess.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/expui/BiorthBess.cc b/expui/BiorthBess.cc index 82a0c759..ce35f012 100644 --- a/expui/BiorthBess.cc +++ b/expui/BiorthBess.cc @@ -1,3 +1,5 @@ +#include + #include #include #include From 03cb0cf6a034ca673b7398046604f5bd5cff0f40 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Fri, 27 Sep 2024 14:16:40 -0400 Subject: [PATCH 27/80] Make the default nmax=12, rather than nmaxfid. More sane. --- exputil/BiorthCyl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exputil/BiorthCyl.cc b/exputil/BiorthCyl.cc index cd4777cb..3bae390a 100644 --- a/exputil/BiorthCyl.cc +++ b/exputil/BiorthCyl.cc @@ -54,7 +54,7 @@ BiorthCyl::BiorthCyl(const YAML::Node& conf) : conf(conf) else nmaxfid = 256; if (conf["nmax"]) nmax = conf["nmax"].as(); - else nmax = nmaxfid; + else nmax = 12; if (conf["numr"]) numr = conf["numr"].as(); else numr = 2000; From b30361d0911ef01a4eae6c4dd8629f7d1a52a5ef Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Fri, 27 Sep 2024 16:09:40 -0400 Subject: [PATCH 28/80] OutVel should put its file in outdir for consistence --- src/OutVel.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OutVel.cc b/src/OutVel.cc index cd877e4d..545d99b0 100644 --- a/src/OutVel.cc +++ b/src/OutVel.cc @@ -39,7 +39,7 @@ OutVel::OutVel(const YAML::Node& conf) : Output(conf) // Target output file // - outfile = "velcoef." + tcomp->name + "." + runtag; + outfile = outdir + "velcoef." + tcomp->name + "." + runtag; // Check for valid model type // From 37d63be74afe97e4d088d738f29aee6c606c94ce Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Fri, 27 Sep 2024 23:21:26 -0400 Subject: [PATCH 29/80] Updates for setMatrix() method for field coefficient types which need to set tensors --- expui/Coefficients.H | 8 ++++---- expui/Coefficients.cc | 10 +++++----- pyEXP/CoefWrappers.cc | 27 +++++++++++++++++++++++---- pyEXP/TensorToArray.H | 26 ++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 13 deletions(-) diff --git a/expui/Coefficients.H b/expui/Coefficients.H index dbfdf99c..ba8eefea 100644 --- a/expui/Coefficients.H +++ b/expui/Coefficients.H @@ -1015,13 +1015,13 @@ namespace CoefClasses virtual Eigen::VectorXcd& getData(double time); //! Operator for pyEXP - SphFldStruct::coefType& getMatrix(double time); + SphFldStruct::dataType getMatrix(double time); //! Set coefficient matrix at given time virtual void setData(double time, Eigen::Ref arr); //! Natural data setter for pyEXP - void setMatrix(double time, const SphFldStruct::coefType& mat); + void setMatrix(double time, SphFldStruct::dataType& mat); //! Interpolate coefficient matrix at given time std::tuple interpolate(double time) @@ -1142,13 +1142,13 @@ namespace CoefClasses virtual Eigen::VectorXcd& getData(double time); //! Operator for pyEXP - CylFldStruct::coefType & getMatrix(double time); + CylFldStruct::dataType getMatrix(double time); //! Set coefficient matrix at given time virtual void setData(double time, Eigen::Ref arr); //! Natural data setter for pyEXP - void setMatrix(double time, const CylFldStruct::coefType& mat); + void setMatrix(double time, CylFldStruct::dataType& mat); //! Interpolate coefficient matrix at given time std::tuple interpolate(double time) diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index d141554b..54f65f8d 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -495,7 +495,7 @@ namespace CoefClasses if (it == coefs.end()) { std::ostringstream str; - str << "SphCoefs::setMatrix: requested time=" << time << " not found"; + str << "SphCoefs::setData: requested time=" << time << " not found"; throw std::runtime_error(str.str()); } else { it->second->store = dat; @@ -2735,7 +2735,7 @@ namespace CoefClasses return arr; } - SphFldStruct::coefType & SphFldCoefs::getMatrix(double time) + SphFldStruct::dataType SphFldCoefs::getMatrix(double time) { auto it = coefs.find(roundTime(time)); @@ -2766,7 +2766,7 @@ namespace CoefClasses } } - void SphFldCoefs::setMatrix(double time, const SphFldStruct::coefType& dat) + void SphFldCoefs::setMatrix(double time, SphFldStruct::dataType& dat) { auto it = coefs.find(roundTime(time)); @@ -3175,7 +3175,7 @@ namespace CoefClasses return arr; } - CylFldStruct::coefType & CylFldCoefs::getMatrix(double time) + CylFldStruct::dataType CylFldCoefs::getMatrix(double time) { auto it = coefs.find(roundTime(time)); @@ -3205,7 +3205,7 @@ namespace CoefClasses } } - void CylFldCoefs::setMatrix(double time, const CylFldStruct::coefType& dat) + void CylFldCoefs::setMatrix(double time, CylFldStruct::dataType& dat) { auto it = coefs.find(roundTime(time)); diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index 3027933b..0dab00f7 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -1399,7 +1399,12 @@ void CoefficientClasses(py::module &m) { SphFldCoefs instance )") .def("__call__", - &CoefClasses::SphFldCoefs::getMatrix, + [](CoefClasses::SphFldCoefs& A, double time) + { + // Need a copy here + auto M = A.getMatrix(time); + return make_ndarray>(M); + }, R"( Return the coefficient tensor for the desired time. @@ -1420,7 +1425,12 @@ void CoefficientClasses(py::module &m) { )", py::arg("time")) .def("setMatrix", - &CoefClasses::SphFldCoefs::setMatrix, + [](CoefClasses::SphFldCoefs& A, double time, + py::array_t> mat) + { + auto M = createTensor>(mat); + A.setMatrix(time, M); + }, R"( Enter and/or rewrite the coefficient tensor at the provided time @@ -1474,7 +1484,11 @@ void CoefficientClasses(py::module &m) { CylFldCoefs instance )") .def("__call__", - &CoefClasses::CylFldCoefs::getMatrix, + [](CoefClasses::CylFldCoefs& A, double time) + { + auto M = A.getMatrix(time); // Need a copy here + return make_ndarray>(M); + }, R"( Return the coefficient tensor for the desired time. @@ -1495,7 +1509,12 @@ void CoefficientClasses(py::module &m) { )", py::arg("time")) .def("setMatrix", - &CoefClasses::CylFldCoefs::setMatrix, + [](CoefClasses::CylFldCoefs& A, double time, + py::array_t> mat) + { + auto M = createTensor>(mat); + A.setMatrix(time, M); + }, R"( Enter and/or rewrite the coefficient tensor at the provided time diff --git a/pyEXP/TensorToArray.H b/pyEXP/TensorToArray.H index 086d0922..268f7453 100644 --- a/pyEXP/TensorToArray.H +++ b/pyEXP/TensorToArray.H @@ -53,4 +53,30 @@ py::array_t make_ndarray4(Eigen::Tensor& mat) ); } +template +Eigen::Tensor createTensor(py::array_t& in) +{ + // Request a buffer descriptor from Python + pybind11::buffer_info buffer_info = in.request(); + + // Extract the data + T *data = static_cast(buffer_info.ptr); + + // Get the data shape + std::vector shape = buffer_info.shape; + + // Reorder the data to satisfy the col-major Eigen::Tensor ordering + // + Eigen::Tensor tensor(shape[0], shape[1], shape[2]); + for (int i=0, c=0; i Date: Sat, 28 Sep 2024 09:26:03 -0400 Subject: [PATCH 30/80] Remove Eigen::Ref from setMatrix to allow more flexibility --- expui/Coefficients.H | 24 ++++++++++++------------ expui/Coefficients.cc | 20 ++++++++++---------- pyEXP/CoefWrappers.cc | 14 +++++++------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/expui/Coefficients.H b/expui/Coefficients.H index ba8eefea..3fafa2dc 100644 --- a/expui/Coefficients.H +++ b/expui/Coefficients.H @@ -111,7 +111,7 @@ namespace CoefClasses virtual Eigen::VectorXcd& getData(double time) = 0; //! Set coefficient store at given time to the provided matrix - virtual void setData(double time, Eigen::Ref data) = 0; + virtual void setData(double time, Eigen::VectorXcd& data) = 0; //! Interpolate coefficient matrix at given time std::tuple interpolate(double time); @@ -273,10 +273,10 @@ namespace CoefClasses Eigen::MatrixXcd& getMatrix(double time); //! Set coefficient matrix at given time - virtual void setData(double time, Eigen::Ref arr); + virtual void setData(double time, Eigen::VectorXcd& arr); //! Natural data setter for pyEXP - void setMatrix(double time, Eigen::Ref mat); + void setMatrix(double time, Eigen::MatrixXcd& mat); //! Interpolate coefficient matrix at given time std::tuple interpolate(double time) @@ -411,10 +411,10 @@ namespace CoefClasses Eigen::MatrixXcd& getMatrix(double time); //! Set coefficient matrix at given time - virtual void setData(double time, Eigen::Ref arr); + virtual void setData(double time, Eigen::VectorXcd& arr); //! For pyEXP - void setMatrix(double time, Eigen::Ref arr); + void setMatrix(double time, Eigen::MatrixXcd& arr); //! Interpolate coefficient matrix at given time std::tuple interpolate(double time) @@ -542,7 +542,7 @@ namespace CoefClasses virtual Eigen3d& getTensor(double time); //! Set coefficient matrix at given time - virtual void setData(double time, Eigen::Ref dat); + virtual void setData(double time, Eigen::VectorXcd& dat); //! Native version virtual void setTensor(double time, const Eigen3d& dat); @@ -673,7 +673,7 @@ namespace CoefClasses virtual Eigen3d& getTensor(double time); //! Set coefficient matrix at given time - virtual void setData(double time, Eigen::Ref dat); + virtual void setData(double time, Eigen::VectorXcd& dat); //! Native version virtual void setTensor(double time, const Eigen3d& dat); @@ -810,9 +810,9 @@ namespace CoefClasses /** Set coefficient matrix at given time. This is for pybind11, since the operator() will not allow lvalue assignment, it seems. */ - virtual void setData(double time, Eigen::Ref arr); + virtual void setData(double time, Eigen::VectorXcd& arr); - void setArray(double time, Eigen::Ref arr) + void setArray(double time, Eigen::VectorXcd& arr) { setData(time, arr); } //! Get coefficient structure at a given time @@ -916,7 +916,7 @@ namespace CoefClasses /** Set coefficient matrix at given time. This is for pybind11, since the operator() will not allow lvalue assignment, it seems. */ - virtual void setData(double time, Eigen::Ref arr); + virtual void setData(double time, Eigen::VectorXcd& arr); //! Get coefficient structure at a given time virtual std::shared_ptr getCoefStruct(double time) @@ -1018,7 +1018,7 @@ namespace CoefClasses SphFldStruct::dataType getMatrix(double time); //! Set coefficient matrix at given time - virtual void setData(double time, Eigen::Ref arr); + virtual void setData(double time, Eigen::VectorXcd& arr); //! Natural data setter for pyEXP void setMatrix(double time, SphFldStruct::dataType& mat); @@ -1145,7 +1145,7 @@ namespace CoefClasses CylFldStruct::dataType getMatrix(double time); //! Set coefficient matrix at given time - virtual void setData(double time, Eigen::Ref arr); + virtual void setData(double time, Eigen::VectorXcd& arr); //! Natural data setter for pyEXP void setMatrix(double time, CylFldStruct::dataType& mat); diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index 54f65f8d..c83dc539 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -489,7 +489,7 @@ namespace CoefClasses return mat; } - void SphCoefs::setData(double time, Eigen::Ref dat) + void SphCoefs::setData(double time, Eigen::VectorXcd& dat) { auto it = coefs.find(roundTime(time)); @@ -504,7 +504,7 @@ namespace CoefClasses } } - void SphCoefs::setMatrix(double time, Eigen::Ref dat) + void SphCoefs::setMatrix(double time, Eigen::MatrixXcd& dat) { auto it = coefs.find(roundTime(time)); @@ -911,7 +911,7 @@ namespace CoefClasses return mat; } - void CylCoefs::setData(double time, Eigen::Ref dat) + void CylCoefs::setData(double time, Eigen::VectorXcd& dat) { auto it = coefs.find(roundTime(time)); @@ -926,7 +926,7 @@ namespace CoefClasses } } - void CylCoefs::setMatrix(double time, Eigen::Ref dat) + void CylCoefs::setMatrix(double time, Eigen::MatrixXcd& dat) { auto it = coefs.find(roundTime(time)); @@ -1267,7 +1267,7 @@ namespace CoefClasses return arr; } - void SlabCoefs::setData(double time, Eigen::Ref dat) + void SlabCoefs::setData(double time, Eigen::VectorXcd& dat) { auto it = coefs.find(roundTime(time)); @@ -1619,7 +1619,7 @@ namespace CoefClasses return arr; } - void CubeCoefs::setData(double time, Eigen::Ref dat) + void CubeCoefs::setData(double time, Eigen::VectorXcd& dat) { auto it = coefs.find(roundTime(time)); @@ -2007,7 +2007,7 @@ namespace CoefClasses return arr; } - void TrajectoryData::setData(double time, Eigen::Ref dat) + void TrajectoryData::setData(double time, Eigen::VectorXcd& dat) { auto it = coefs.find(roundTime(time)); @@ -2253,7 +2253,7 @@ namespace CoefClasses return arr; } - void TableData::setData(double time, Eigen::Ref dat) + void TableData::setData(double time, Eigen::VectorXcd& dat) { auto it = coefs.find(roundTime(time)); @@ -2751,7 +2751,7 @@ namespace CoefClasses return *mat; } - void SphFldCoefs::setData(double time, Eigen::Ref dat) + void SphFldCoefs::setData(double time, Eigen::VectorXcd& dat) { auto it = coefs.find(roundTime(time)); @@ -3190,7 +3190,7 @@ namespace CoefClasses return *mat; } - void CylFldCoefs::setData(double time, Eigen::Ref dat) + void CylFldCoefs::setData(double time, Eigen::VectorXcd& dat) { auto it = coefs.find(roundTime(time)); diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index 0dab00f7..f78d9c61 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -155,7 +155,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE_PURE(Eigen::VectorXcd&, Coefs, getData, time); } - void setData(double time, Eigen::Ref array) override { + void setData(double time, Eigen::VectorXcd& array) override { PYBIND11_OVERRIDE_PURE(void, Coefs, setData, time, array); } @@ -231,7 +231,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE(Eigen::VectorXcd&, SphCoefs, getData, time); } - void setData(double time, Eigen::Ref array) override { + void setData(double time, Eigen::VectorXcd& array) override { PYBIND11_OVERRIDE(void, SphCoefs, setData, time, array); } @@ -310,7 +310,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE(Eigen::VectorXcd&, CylCoefs, getData, time); } - void setData(double time, Eigen::Ref array) override { + void setData(double time, Eigen::VectorXcd& array) override { PYBIND11_OVERRIDE(void, CylCoefs, setData, time, array); } @@ -388,7 +388,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE(Eigen::VectorXcd&, SlabCoefs, getData, time); } - void setData(double time, Eigen::Ref array) override { + void setData(double time, Eigen::VectorXcd& array) override { PYBIND11_OVERRIDE(void, SlabCoefs, setData, time, array); } @@ -467,7 +467,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE(Eigen::VectorXcd&, CubeCoefs, getData, time); } - void setData(double time, Eigen::Ref array) override { + void setData(double time, Eigen::VectorXcd& array) override { PYBIND11_OVERRIDE(void, CubeCoefs, setData, time, array); } @@ -546,7 +546,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE(Eigen::VectorXcd&, TableData, getData, time); } - void setData(double time, Eigen::Ref array) override { + void setData(double time, Eigen::VectorXcd& array) override { PYBIND11_OVERRIDE(void, TableData, setData, time, array); } @@ -624,7 +624,7 @@ void CoefficientClasses(py::module &m) { PYBIND11_OVERRIDE(Eigen::VectorXcd&, TrajectoryData, getData, time); } - void setData(double time, Eigen::Ref array) override { + void setData(double time, Eigen::VectorXcd& array) override { PYBIND11_OVERRIDE(void, TrajectoryData, setData, time, array); } From 8ab5ba24e3b8242467b424b31a5f01202ed569f6 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sat, 28 Sep 2024 09:58:41 -0400 Subject: [PATCH 31/80] Fix typos in diagnostic errors [no ci] --- expui/Coefficients.cc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index c83dc539..035b95f8 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -917,7 +917,7 @@ namespace CoefClasses if (it == coefs.end()) { std::ostringstream str; - str << "CylCoefs::setMatrix: requested time=" << time << " not found"; + str << "CylCoefs::setData: requested time=" << time << " not found"; throw std::runtime_error(str.str()); } else { it->second->store = dat; @@ -1273,7 +1273,7 @@ namespace CoefClasses if (it == coefs.end()) { std::ostringstream str; - str << "CylCoefs::setMatrix: requested time=" << time << " not found"; + str << "CylCoefs::setData: requested time=" << time << " not found"; throw std::runtime_error(str.str()); } else { it->second->store = dat; @@ -1288,7 +1288,7 @@ namespace CoefClasses if (it == coefs.end()) { std::ostringstream str; - str << "CylCoefs::setMatrix: requested time=" << time << " not found"; + str << "SlabCoefs::setTensor: requested time=" << time << " not found"; throw std::runtime_error(str.str()); } else { it->second->allocate(); // Assign storage for the flattened tensor @@ -1625,7 +1625,7 @@ namespace CoefClasses if (it == coefs.end()) { std::ostringstream str; - str << "CylCoefs::setMatrix: requested time=" << time << " not found"; + str << "CubeCoefs::setData: requested time=" << time << " not found"; throw std::runtime_error(str.str()); } else { it->second->store = dat; @@ -1640,7 +1640,7 @@ namespace CoefClasses if (it == coefs.end()) { std::ostringstream str; - str << "CylCoefs::setMatrix: requested time=" << time << " not found"; + str << "CubeCoefs::setTensor: requested time=" << time << " not found"; throw std::runtime_error(str.str()); } else { it->second->allocate(); // Assign storage for the flattened tensor @@ -2013,7 +2013,7 @@ namespace CoefClasses if (it == coefs.end()) { std::ostringstream str; - str << "TrajectoryData::setMatrix: requested time=" << time << " not found"; + str << "TrajectoryData::setData: requested time=" << time << " not found"; throw std::runtime_error(str.str()); } else { it->second->store = dat; @@ -2259,7 +2259,7 @@ namespace CoefClasses if (it == coefs.end()) { std::ostringstream str; - str << "TableData::setMatrix: requested time=" << time << " not found"; + str << "TableData::setData: requested time=" << time << " not found"; throw std::runtime_error(str.str()); } else { it->second->store = dat; @@ -2757,7 +2757,7 @@ namespace CoefClasses if (it == coefs.end()) { std::ostringstream str; - str << "SphVelCoefs::setMatrix: requested time=" << time << " not found"; + str << "SphFldCoefs::setData: requested time=" << time << " not found"; throw std::runtime_error(str.str()); } else { it->second->store = dat; @@ -2772,7 +2772,7 @@ namespace CoefClasses if (it == coefs.end()) { std::ostringstream str; - str << "SphVelCoefs::setMatrix: requested time=" << time << " not found"; + str << "SphFldCoefs::setMatrix: requested time=" << time << " not found"; throw std::runtime_error(str.str()); } else { it->second->allocate(); From 828f583f2fa6b6e19c50a015094d7afc5dbe5c62 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Fri, 4 Oct 2024 22:16:40 -0400 Subject: [PATCH 32/80] Change to variable number of fields in SphFldStruct and CylFldStruct constructors --- expui/Coefficients.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/expui/Coefficients.cc b/expui/Coefficients.cc index 035b95f8..a09362f9 100644 --- a/expui/Coefficients.cc +++ b/expui/Coefficients.cc @@ -2745,7 +2745,7 @@ namespace CoefClasses arr = it->second->store; int ldim = (Lmax+1)*(Lmax+2)/2; mat = std::make_shared - (arr.data(), 4, ldim, Nmax); + (arr.data(), Nfld, ldim, Nmax); } return *mat; @@ -3184,7 +3184,7 @@ namespace CoefClasses } else { arr = it->second->store; int mdim = Mmax + 1; - mat = std::make_shared(arr.data(), 3, mdim, Nmax); + mat = std::make_shared(arr.data(), Nfld, mdim, Nmax); } return *mat; From 1212151e0f3d6431fad115aac98458c46a1c3485 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Fri, 4 Oct 2024 22:18:48 -0400 Subject: [PATCH 33/80] Change names of Eigen::Tensor wrappers for consistency --- pyEXP/BasisWrappers.cc | 2 +- pyEXP/CoefWrappers.cc | 20 ++++++++-------- pyEXP/FieldWrappers.cc | 2 +- pyEXP/TensorToArray.H | 54 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 60 insertions(+), 18 deletions(-) diff --git a/pyEXP/BasisWrappers.cc b/pyEXP/BasisWrappers.cc index df9e8346..e0b72ab2 100644 --- a/pyEXP/BasisWrappers.cc +++ b/pyEXP/BasisWrappers.cc @@ -2014,7 +2014,7 @@ void BasisFactoryClasses(py::module &m) std::tie(T, O) = BasisClasses::IntegrateOrbits(tinit, tfinal, h, ps, bfe, F, stride); - py::array_t ret = make_ndarray(O); + py::array_t ret = make_ndarray3(O); return std::tuple>(T, ret); }, R"( diff --git a/pyEXP/CoefWrappers.cc b/pyEXP/CoefWrappers.cc index f78d9c61..21408b3b 100644 --- a/pyEXP/CoefWrappers.cc +++ b/pyEXP/CoefWrappers.cc @@ -1263,7 +1263,7 @@ void CoefficientClasses(py::module &m) { [](CoefClasses::SphCoefs& A) { auto M = A.getAllCoefs(); // Need a copy here - py::array_t> ret = make_ndarray>(M); + py::array_t> ret = make_ndarray3>(M); return ret; }, R"( @@ -1338,7 +1338,7 @@ void CoefficientClasses(py::module &m) { [](CoefClasses::CylCoefs& A) { auto M = A.getAllCoefs(); // Need a copy here - py::array_t> ret = make_ndarray>(M); + py::array_t> ret = make_ndarray3>(M); return ret; }, R"( @@ -1403,7 +1403,7 @@ void CoefficientClasses(py::module &m) { { // Need a copy here auto M = A.getMatrix(time); - return make_ndarray>(M); + return make_ndarray3>(M); }, R"( Return the coefficient tensor for the desired time. @@ -1428,7 +1428,7 @@ void CoefficientClasses(py::module &m) { [](CoefClasses::SphFldCoefs& A, double time, py::array_t> mat) { - auto M = createTensor>(mat); + auto M = make_tensor3>(mat); A.setMatrix(time, M); }, R"( @@ -1438,8 +1438,8 @@ void CoefficientClasses(py::module &m) { ---------- time : float snapshot time corresponding to the the coefficient matrix - mat : numpy.ndarray - the new coefficient array. + mat : numpy.ndarray + the new coefficient array. Returns ------- @@ -1487,7 +1487,7 @@ void CoefficientClasses(py::module &m) { [](CoefClasses::CylFldCoefs& A, double time) { auto M = A.getMatrix(time); // Need a copy here - return make_ndarray>(M); + return make_ndarray3>(M); }, R"( Return the coefficient tensor for the desired time. @@ -1512,7 +1512,7 @@ void CoefficientClasses(py::module &m) { [](CoefClasses::CylFldCoefs& A, double time, py::array_t> mat) { - auto M = createTensor>(mat); + auto M = make_tensor3>(mat); A.setMatrix(time, M); }, R"( @@ -1522,8 +1522,8 @@ void CoefficientClasses(py::module &m) { ---------- time : float snapshot time corresponding to the the coefficient matrix - mat : numpy.ndarray - the new coefficient array. + mat : numpy.ndarray + the new coefficient array. Returns ------- diff --git a/pyEXP/FieldWrappers.cc b/pyEXP/FieldWrappers.cc index 995f9135..db5d0fec 100644 --- a/pyEXP/FieldWrappers.cc +++ b/pyEXP/FieldWrappers.cc @@ -387,7 +387,7 @@ void FieldGeneratorClasses(py::module &m) { auto vols = A.volumes(basis, coefs); for (auto & v : vols) { for (auto & u : v.second) { - ret[v.first][u.first] = make_ndarray(u.second); + ret[v.first][u.first] = make_ndarray3(u.second); } } diff --git a/pyEXP/TensorToArray.H b/pyEXP/TensorToArray.H index 268f7453..765148ea 100644 --- a/pyEXP/TensorToArray.H +++ b/pyEXP/TensorToArray.H @@ -3,7 +3,7 @@ //! Helper function that maps the Eigen::Tensor into an numpy.ndarray template -py::array_t make_ndarray(Eigen::Tensor& mat) +py::array_t make_ndarray3(Eigen::Tensor& mat) { // Get the tensor dimenions auto dims = mat.dimensions(); @@ -54,10 +54,10 @@ py::array_t make_ndarray4(Eigen::Tensor& mat) } template -Eigen::Tensor createTensor(py::array_t& in) +Eigen::Tensor make_tensor3(py::array_t& in) { // Request a buffer descriptor from Python - pybind11::buffer_info buffer_info = in.request(); + py::buffer_info buffer_info = in.request(); // Extract the data T *data = static_cast(buffer_info.ptr); @@ -65,12 +65,20 @@ Eigen::Tensor createTensor(py::array_t& in) // Get the data shape std::vector shape = buffer_info.shape; + // Check rank + if (shape.size() != 3) { + std::ostringstream sout; + sout << "make_tensor3: tensor rank must be 3, found " + << shape.size(); + throw std::runtime_error(sout.str()); + } + // Reorder the data to satisfy the col-major Eigen::Tensor ordering // Eigen::Tensor tensor(shape[0], shape[1], shape[2]); - for (int i=0, c=0; i createTensor(py::array_t& in) return tensor; } +template +Eigen::Tensor make_tensor4(py::array_t array) +{ + // Request a buffer descriptor from Python + py::buffer_info buffer_info = array.request(); + + // Get the array dimenions + T *data = static_cast(buffer_info.ptr); + std::vector shape = buffer_info.shape; + + // Check rank + if (shape.size() != 4) { + std::ostringstream sout; + sout << "make_tensor4: tensor rank must be 4, found " + << shape.size(); + throw std::runtime_error(sout.str()); + } + + // Build result tensor with col-major ordering + Eigen::Tensor tensor(shape[0], shape[1], shape[2], shape[3]); + for (int i=0, l=0; i < shape[0]; i++) { + for (int j=0; j < shape[1]; j++) { + for (int k=0; k < shape[2]; k++) { + for (int l=0; l < shape[3]; k++) { + tensor(i, j, k, l) = data[l++]; + } + } + } + } + + return tensor; +} + + #endif From cdd28977d8a7704cd48dd9719015af887ede5883 Mon Sep 17 00:00:00 2001 From: mdw Date: Thu, 10 Oct 2024 11:26:48 -0400 Subject: [PATCH 34/80] Do not enable CUDA twice --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 75a4ec47..2c95fb0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,7 +91,7 @@ endif() # Optional CUDA support if(ENABLE_CUDA) - enable_language(CUDA) + # enable_language(CUDA) find_package(CUDAToolkit REQUIRED) message(STATUS "Using CUDA architectures ${CMAKE_CUDA_ARCHITECTURES} by default. Please set the CUDAARCHS environment variable to override this.") set(CMAKE_CUDA_FLAGS_RELEASE "-O3 -w" From d2a23c56dcbbb75e9e96667c10a427e8f90181a3 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 28 Oct 2024 11:10:07 -0400 Subject: [PATCH 35/80] Tests for 2d basis functions --- exputil/EmpCyl2d.cc | 47 ++++-------------- pyEXP/TensorToArray.H | 31 ------------ src/user/CMakeLists.txt | 6 +-- utils/SL/CMakeLists.txt | 3 +- utils/SL/EOF2d.cc | 19 ++++---- utils/SL/expontest.cc | 105 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 129 insertions(+), 82 deletions(-) create mode 100644 utils/SL/expontest.cc diff --git a/exputil/EmpCyl2d.cc b/exputil/EmpCyl2d.cc index 1eaf0635..16a419f0 100644 --- a/exputil/EmpCyl2d.cc +++ b/exputil/EmpCyl2d.cc @@ -836,7 +836,7 @@ void EmpCyl2d::create_tables() for (int l=0; ldens(rr) * basis->potl(m, j, rr) * basis->potl(m, l, rr) - / sqrt(basis->norm(j, m)*basis->norm(l, m)*4.0); + / sqrt(basis->norm(j, m)*basis->norm(l, m)); } } } @@ -873,15 +873,9 @@ void EmpCyl2d::create_tables() if (m==0) xgrid[i] = r; for (int n=0; npotl(m, n, r) / sqrt(basis->norm(n, m)*0.5); - den(n) = basis->dens(m, n, r) / sqrt(basis->norm(n, m)*2.0); - dph(n) = basis->dpot(m, n, r) / sqrt(basis->norm(n, m)*0.5); -======= - pot(n) = basis->potl(m, n, r) / sqrt(basis->norm(n, m)*0.5/M_PI); - den(n) = basis->dens(m, n, r) / sqrt(basis->norm(n, m)*2.0*M_PI); - dph(n) = basis->dpot(m, n, r) / sqrt(basis->norm(n, m)*0.5/M_PI); ->>>>>>> Stashed changes + pot(n) = basis->potl(m, n, r) / sqrt(basis->norm(n, m)); + den(n) = basis->dens(m, n, r) / sqrt(basis->norm(n, m)); + dph(n) = basis->dpot(m, n, r) / sqrt(basis->norm(n, m)); } pot = U.transpose() * pot; @@ -1209,11 +1203,7 @@ double EmpCyl2d::get_potl(double r, int M, int N) checkMN(M, N, "get_potl"); if (basis_test) { -<<<<<<< Updated upstream - return basis->potl(M, N, r)/sqrt(basis->norm(N, M)*0.5); -======= - return basis->potl(M, N, r)/sqrt(basis->norm(N, M)*0.5/M_PI); ->>>>>>> Stashed changes + return basis->potl(M, N, r)/sqrt(basis->norm(N, M)); } int lo, hi; @@ -1228,11 +1218,7 @@ double EmpCyl2d::get_dens(double r, int M, int N) checkMN(M, N, "get_dens"); if (basis_test) { -<<<<<<< Updated upstream - return basis->dens(M, N, r)/sqrt(basis->norm(N, M)*2.0); -======= - return basis->dens(M, N, r)/sqrt(basis->norm(N, M)*2.0*M_PI); ->>>>>>> Stashed changes + return basis->dens(M, N, r)/sqrt(basis->norm(N, M)); } int lo, hi; @@ -1247,11 +1233,7 @@ double EmpCyl2d::get_dpot(double r, int M, int N) checkMN(M, N, "get_dpot"); if (basis_test) { -<<<<<<< Updated upstream - return basis->dpot(M, N, r)/sqrt(basis->norm(N, M)*0.5); -======= - return basis->dpot(M, N, r)/sqrt(basis->norm(N, M)*2.0*M_PI); ->>>>>>> Stashed changes + return basis->dpot(M, N, r)/sqrt(basis->norm(N, M)); } int lo, hi; @@ -1325,11 +1307,7 @@ void EmpCyl2d::checkCoefs() for (int j=0; jdens(rr) * get_potl(rr, 0, j); -<<<<<<< Updated upstream - coef0(j) += fac * disk->dens(rr) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)*0.5); -======= - coef0(j) += fac * disk->dens(rr) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)*2.0*M_PI); ->>>>>>> Stashed changes + coef0(j) += fac * disk->dens(rr) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)); } } @@ -1353,13 +1331,8 @@ void EmpCyl2d::checkCoefs() for (int j=0; jpotl(0, j, rr) / sqrt(basis->norm(j, 0)*0.5); - zz += coef0(j) * basis->dens(0, j, rr) / sqrt(basis->norm(j, 0)*2.0); -======= - yy += coef0(j) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)*2.0*M_PI); - zz += coef0(j) * basis->dens(0, j, rr) / sqrt(basis->norm(j, 0)*0.5/M_PI); ->>>>>>> Stashed changes + yy += coef0(j) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)); + zz += coef0(j) * basis->dens(0, j, rr) / sqrt(basis->norm(j, 0)); } std::cout << std::setw(16) << rr diff --git a/pyEXP/TensorToArray.H b/pyEXP/TensorToArray.H index d2ba6020..e10910be 100644 --- a/pyEXP/TensorToArray.H +++ b/pyEXP/TensorToArray.H @@ -27,37 +27,6 @@ py::array_t make_ndarray3(Eigen::Tensor& mat) ); } -template -Eigen::Tensor make_tensor3(py::array_t array) -{ - // Request a buffer descriptor from Python - py::buffer_info buffer_info = array.request(); - - // Get the array dimenions - T *data = static_cast(buffer_info.ptr); - std::vector shape = buffer_info.shape; - - // Check rank - if (shape.size() != 3) { - std::ostringstream sout; - sout << "make_tensor3: tensor rank must be 3, found " - << shape.size(); - throw std::runtime_error(sout.str()); - } - - // Build result tensor with col-major ordering - Eigen::Tensor tensor(shape[0], shape[1], shape[2]); - for (int i=0, l=0; i < shape[0]; i++) { - for (int j=0; j < shape[1]; j++) { - for (int k=0; k < shape[2]; k++) { - tensor(i, j, k) = data[l++]; - } - } - } - - return tensor; -} - //! Helper function that maps the Eigen::Tensor into an numpy.ndarray template diff --git a/src/user/CMakeLists.txt b/src/user/CMakeLists.txt index 3f2a5ff8..3af49426 100644 --- a/src/user/CMakeLists.txt +++ b/src/user/CMakeLists.txt @@ -14,7 +14,7 @@ set (common_LINKLIBS ${DEP_LIB} OpenMP::OpenMP_CXX MPI::MPI_CXX if(ENABLE_CUDA) list(APPEND common_INCLUDE_DIRS ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES} ${CUDAToolkit_INCLUDE_DIRS}) - list(APPEND common_LINKLIB CUDA::cudart CUDA::nvToolsExt) + list(APPEND common_LINKLIBS CUDA::cudart CUDA::nvToolsExt) endif() set(user_SRC UserTest.cc) @@ -25,8 +25,8 @@ set(halo_SRC UserHalo.cc) set(mndisk_SRC UserMNdisk.cc) set(mwgala_SRC UserMW.cc) -if (ENABLE_CUDA) - set(cudatest_Src UserTestCuda.cc cudaUserTest.cu) +if(ENABLE_CUDA) + set(cudatest_SRC UserTestCuda.cc cudaUserTest.cu) endif() foreach(mlib ${USER_MODULES}) diff --git a/utils/SL/CMakeLists.txt b/utils/SL/CMakeLists.txt index 55fe3061..0c9e84ad 100644 --- a/utils/SL/CMakeLists.txt +++ b/utils/SL/CMakeLists.txt @@ -1,6 +1,6 @@ set(bin_PROGRAMS slcheck slshift orthochk diskpot qtest eoftest - oftest slabchk) + oftest slabchk expontst) set(common_LINKLIB OpenMP::OpenMP_CXX MPI::MPI_CXX yaml-cpp exputil ${VTK_LIBRARIES}) @@ -30,6 +30,7 @@ add_executable(diskpot diskpot.cc CylindricalDisk.cc SLSphere.cc) add_executable(qtest qtest.cc) add_executable(eoftest EOF2d.cc) add_executable(oftest oftest.cc) +add_executable(expontst expontest.cc) foreach(program ${bin_PROGRAMS}) target_link_libraries(${program} ${common_LINKLIB}) diff --git a/utils/SL/EOF2d.cc b/utils/SL/EOF2d.cc index 9db5a1e3..e2aeceb8 100644 --- a/utils/SL/EOF2d.cc +++ b/utils/SL/EOF2d.cc @@ -17,7 +17,7 @@ int main(int argc, char** argv) { bool logr = false, cmap = false, ortho = false, plane = false; int numr, mmax, nmaxfid, nmax, knots, M, N, nradial, nout; - double A, rmin, rmax, rout; + double scale, rmin, rmax, rout; std::string filename, config, biorth; // Parse command line @@ -51,8 +51,8 @@ int main(int argc, char** argv) cxxopts::value(N)->default_value("256")) ("n,nradial", "Radial order for vertical potential output", cxxopts::value(nradial)->default_value("0")) - ("A,length", "characteristic disk scale length", - cxxopts::value(A)->default_value("1.0")) + ("s,length", "characteristic disk scale length for mapping", + cxxopts::value(scale)->default_value("1.0")) ("mmax", "maximum number of angular harmonics in the expansion", cxxopts::value(mmax)->default_value("4")) ("nmaxfid", "maximum number of radial basis harmonics for EOF construction", @@ -60,13 +60,13 @@ int main(int argc, char** argv) ("nmax", "maximum number of radial harmonics in the expansion", cxxopts::value(nmax)->default_value("10")) ("numr", "radial knots for the SL grid", - cxxopts::value(numr)->default_value("4000")) + cxxopts::value(numr)->default_value("8000")) ("r,rmin", "minimum radius for the SL grid", cxxopts::value(rmin)->default_value("0.0001")) ("R,rmax", "maximum radius for the SL grid", cxxopts::value(rmax)->default_value("20.0")) ("knots", "Number of Legendre integration knots", - cxxopts::value(knots)->default_value("200")) + cxxopts::value(knots)->default_value("1000")) ("rout", "Outer radius for evaluation", cxxopts::value(rout)->default_value("10.0")) ("nout", "number of points in the output grid per side", @@ -114,7 +114,7 @@ int main(int argc, char** argv) // Make the class instance // - EmpCyl2d emp(mmax, nmaxfid, nmax, knots, numr, rmin, rmax, A, cmap, logr, + EmpCyl2d emp(mmax, nmaxfid, nmax, knots, numr, rmin, rmax, scale, cmap, logr, par, biorth); if (vm.count("basis")) emp.basisTest(true); @@ -253,9 +253,8 @@ int main(int argc, char** argv) for (int n=0; n +#include +#include +#include +#include +#include +#include + +#include + +#include // Hankel computation for potential +#include // Gauss-Legendre quadrature +#include + +int main(int argc, char** argv) +{ + int M=0, N, nout; + double A, rmax, rout; + std::string filename; + + // Parse command line + // + cxxopts::Options options + (argv[0], + "Check PotRZ and QDHT using the exact exponential disk solution\n"); + + options.add_options() + ("h,help", "Print this help message") + ("N,nsize", "Default radial grid size", + cxxopts::value(N)->default_value("256")) + ("A,length", "characteristic disk scale length", + cxxopts::value(A)->default_value("1.0")) + ("rmax", "Outer radius for transform", + cxxopts::value(rmax)->default_value("10.0")) + ("rout", "Outer radius for evaluation", + cxxopts::value(rout)->default_value("10.0")) + ("nout", "number of points in the output grid per side", + cxxopts::value(nout)->default_value("40")) + ("o,filename", "Output filename", + cxxopts::value(filename)->default_value("test.potrz")) + ; + + + //=================== + // Parse options + //=================== + + cxxopts::ParseResult vm; + + try { + vm = options.parse(argc, argv); + } catch (cxxopts::OptionException& e) { + std::cout << "Option error: " << e.what() << std::endl; + return 2; + } + + // Print help message and exit + // + if (vm.count("help")) { + std::cout << options.help() << std::endl << std::endl; + return 1; + } + + // Open output file + // + std::ofstream out(filename); + if (not out) throw std::runtime_error("Could not open output file: " + + filename); + + // Define some representative limits + // + double Rmax = rout; + + // Grid spacing + // + double dR = Rmax/(nout - 1); + + // Potential instance with radially sensitive convergence parameters + // + PotRZ pot(rmax, N, M); + + // Set the functor using a lambda + // + auto dens = [A](double R) { + return -exp(-R/A); + }; + + auto potl = [A](double R) { + double x = 0.5*R/A; + return M_PI*R*(std::cyl_bessel_i(1, x) * std::cyl_bessel_k(0, x) - + std::cyl_bessel_i(0, x) * std::cyl_bessel_k(1, x) ); + }; + + // Write the results + // + for (int i=0; i Date: Mon, 28 Oct 2024 13:38:00 -0400 Subject: [PATCH 36/80] Norm updates --- CMakeLists.txt | 2 +- exputil/EmpCyl2d.cc | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fa5123b2..8634ee40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,7 +94,7 @@ endif() # Optional CUDA support if(ENABLE_CUDA) - # enable_language(CUDA) + enable_language(CUDA) find_package(CUDAToolkit REQUIRED) message(STATUS "Using CUDA architectures ${CMAKE_CUDA_ARCHITECTURES} by default. Please set the CUDAARCHS environment variable to override this.") set(CMAKE_CUDA_FLAGS_RELEASE "-O3 -w" diff --git a/exputil/EmpCyl2d.cc b/exputil/EmpCyl2d.cc index 16a419f0..62633436 100644 --- a/exputil/EmpCyl2d.cc +++ b/exputil/EmpCyl2d.cc @@ -873,9 +873,9 @@ void EmpCyl2d::create_tables() if (m==0) xgrid[i] = r; for (int n=0; npotl(m, n, r) / sqrt(basis->norm(n, m)); - den(n) = basis->dens(m, n, r) / sqrt(basis->norm(n, m)); - dph(n) = basis->dpot(m, n, r) / sqrt(basis->norm(n, m)); + pot(n) = basis->potl(m, n, r) / sqrt(basis->norm(n, m)*0.5/M_PI); + den(n) = basis->dens(m, n, r) / sqrt(basis->norm(n, m)*2.0*M_PI); + dph(n) = basis->dpot(m, n, r) / sqrt(basis->norm(n, m)*0.5/M_PI); } pot = U.transpose() * pot; @@ -1203,7 +1203,7 @@ double EmpCyl2d::get_potl(double r, int M, int N) checkMN(M, N, "get_potl"); if (basis_test) { - return basis->potl(M, N, r)/sqrt(basis->norm(N, M)); + return basis->potl(M, N, r)/sqrt(basis->norm(N, M)*0.5/M_PI); } int lo, hi; @@ -1218,7 +1218,7 @@ double EmpCyl2d::get_dens(double r, int M, int N) checkMN(M, N, "get_dens"); if (basis_test) { - return basis->dens(M, N, r)/sqrt(basis->norm(N, M)); + return basis->dens(M, N, r)/sqrt(basis->norm(N, M)*2.0*M_PI); } int lo, hi; @@ -1233,7 +1233,7 @@ double EmpCyl2d::get_dpot(double r, int M, int N) checkMN(M, N, "get_dpot"); if (basis_test) { - return basis->dpot(M, N, r)/sqrt(basis->norm(N, M)); + return basis->dpot(M, N, r)/sqrt(basis->norm(N, M)*2.0*M_PI); } int lo, hi; @@ -1307,7 +1307,7 @@ void EmpCyl2d::checkCoefs() for (int j=0; jdens(rr) * get_potl(rr, 0, j); - coef0(j) += fac * disk->dens(rr) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)); + coef0(j) += fac * disk->dens(rr) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)*0.5/M_PI); } } @@ -1331,8 +1331,8 @@ void EmpCyl2d::checkCoefs() for (int j=0; jpotl(0, j, rr) / sqrt(basis->norm(j, 0)); - zz += coef0(j) * basis->dens(0, j, rr) / sqrt(basis->norm(j, 0)); + yy += coef0(j) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)*0.5/M_PI); + zz += coef0(j) * basis->dens(0, j, rr) / sqrt(basis->norm(j, 0)*2.0*M_PI); } std::cout << std::setw(16) << rr From 2752d2ea8a2b490eea40e9638051661f7903598a Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 28 Oct 2024 13:50:10 -0400 Subject: [PATCH 37/80] Fix clang options for Linux --- CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8634ee40..9d365908 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,8 +16,6 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") message(STATUS "Clang compiler version is: ${CMAKE_CXX_COMPILER_VERSION}") if(CMAKE_SYSTEM_NAME MATCHES "Linux") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 18.0.0) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") - else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libstdc++") endif() endif() From 7443379b1fd560da375d113cd2eab73a6fd6260f Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sat, 2 Nov 2024 12:16:23 -0400 Subject: [PATCH 38/80] Make the force/coefficient evaluation consistent with the 2d basis norm --- exputil/EmpCyl2d.cc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/exputil/EmpCyl2d.cc b/exputil/EmpCyl2d.cc index 62633436..16a419f0 100644 --- a/exputil/EmpCyl2d.cc +++ b/exputil/EmpCyl2d.cc @@ -873,9 +873,9 @@ void EmpCyl2d::create_tables() if (m==0) xgrid[i] = r; for (int n=0; npotl(m, n, r) / sqrt(basis->norm(n, m)*0.5/M_PI); - den(n) = basis->dens(m, n, r) / sqrt(basis->norm(n, m)*2.0*M_PI); - dph(n) = basis->dpot(m, n, r) / sqrt(basis->norm(n, m)*0.5/M_PI); + pot(n) = basis->potl(m, n, r) / sqrt(basis->norm(n, m)); + den(n) = basis->dens(m, n, r) / sqrt(basis->norm(n, m)); + dph(n) = basis->dpot(m, n, r) / sqrt(basis->norm(n, m)); } pot = U.transpose() * pot; @@ -1203,7 +1203,7 @@ double EmpCyl2d::get_potl(double r, int M, int N) checkMN(M, N, "get_potl"); if (basis_test) { - return basis->potl(M, N, r)/sqrt(basis->norm(N, M)*0.5/M_PI); + return basis->potl(M, N, r)/sqrt(basis->norm(N, M)); } int lo, hi; @@ -1218,7 +1218,7 @@ double EmpCyl2d::get_dens(double r, int M, int N) checkMN(M, N, "get_dens"); if (basis_test) { - return basis->dens(M, N, r)/sqrt(basis->norm(N, M)*2.0*M_PI); + return basis->dens(M, N, r)/sqrt(basis->norm(N, M)); } int lo, hi; @@ -1233,7 +1233,7 @@ double EmpCyl2d::get_dpot(double r, int M, int N) checkMN(M, N, "get_dpot"); if (basis_test) { - return basis->dpot(M, N, r)/sqrt(basis->norm(N, M)*2.0*M_PI); + return basis->dpot(M, N, r)/sqrt(basis->norm(N, M)); } int lo, hi; @@ -1307,7 +1307,7 @@ void EmpCyl2d::checkCoefs() for (int j=0; jdens(rr) * get_potl(rr, 0, j); - coef0(j) += fac * disk->dens(rr) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)*0.5/M_PI); + coef0(j) += fac * disk->dens(rr) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)); } } @@ -1331,8 +1331,8 @@ void EmpCyl2d::checkCoefs() for (int j=0; jpotl(0, j, rr) / sqrt(basis->norm(j, 0)*0.5/M_PI); - zz += coef0(j) * basis->dens(0, j, rr) / sqrt(basis->norm(j, 0)*2.0*M_PI); + yy += coef0(j) * basis->potl(0, j, rr) / sqrt(basis->norm(j, 0)); + zz += coef0(j) * basis->dens(0, j, rr) / sqrt(basis->norm(j, 0)); } std::cout << std::setw(16) << rr From 321dcd8f45df938457bf99c03b72e305231cd105 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Wed, 6 Nov 2024 09:56:08 -0500 Subject: [PATCH 39/80] Updates for standalone VTK file writing --- exputil/CMakeLists.txt | 14 ++-- exputil/VtkGrid.cc | 161 +++++++++++++++++++++++++++++++++++++++++ include/DataGrid.H | 24 +++--- include/VtkGrid.H | 57 ++++++++++++++- 4 files changed, 234 insertions(+), 22 deletions(-) diff --git a/exputil/CMakeLists.txt b/exputil/CMakeLists.txt index 9baa63df..c0722c39 100644 --- a/exputil/CMakeLists.txt +++ b/exputil/CMakeLists.txt @@ -8,10 +8,10 @@ set(UTIL_SRC nrutil.cc elemfunc.cc euler.cc euler_slater.cc # Hankel.cc rotmatrix.cc wordSplit.cc FileUtils.cc BarrierWrapper.cc stack.cc localmpi.cc TableGrid.cc writePVD.cc libvars.cc TransformFFT.cc QDHT.cc YamlCheck.cc parseVersionString.cc EXPmath.cc laguerre_polynomial.cpp - YamlConfig.cc orthoTest.cc OrthoFunction.cc) + YamlConfig.cc orthoTest.cc OrthoFunction.cc VtkGrid.cc) if(HAVE_VTK) - list(APPEND UTIL_SRC VtkGrid.cc VtkPCA.cc) + list(APPEND UTIL_SRC VtkPCA.cc) endif() set(OPTIMIZATION_SRC SimAnn.cc) @@ -51,13 +51,9 @@ set(common_INCLUDE_DIRS $ ${DEP_INC} ${EIGEN3_INCLUDE_DIR} ${HDF5_INCLUDE_DIRS} ${FFTW_INCLUDE_DIRS}) -# set(common_LINKLIBS ${DEP_LIB} OpenMP::OpenMP_CXX MPI::MPI_CXX -# yaml-cpp ${VTK_LIBRARIES} ${HDF5_CXX_LIBRARIES} ${HDF5_LIBRARIES} -# ${HDF5_HL_LIBRARIES} ${FFTW_DOUBLE_LIB}) - -set(common_LINKLIB ${DEP_LIB} OpenMP::OpenMP_CXX MPI::MPI_CXX - yaml-cpp ${VTK_LIBRARIES} ${HDF5_LIBRARIES} ${HDF5_HL_LIBRARIES} - ${FFTW_DOUBLE_LIB}) +set(common_LINKLIB ${DEP_LIB} OpenMP::OpenMP_CXX MPI::MPI_CXX yaml-cpp + ${VTK_LIBRARIES} ${HDF5_CXX_LIBRARIES} ${HDF5_LIBRARIES} + ${HDF5_HL_LIBRARIES} ${FFTW_DOUBLE_LIB}) if(ENABLE_CUDA) list(APPEND common_LINKLIB CUDA::toolkit CUDA::cudart) diff --git a/exputil/VtkGrid.cc b/exputil/VtkGrid.cc index 6ba83799..395e5631 100644 --- a/exputil/VtkGrid.cc +++ b/exputil/VtkGrid.cc @@ -1,5 +1,8 @@ +#include #include +#ifdef HAVE_VTK + VtkGrid::VtkGrid(int nx, int ny, int nz, double xmin, double xmax, double ymin, double ymax, @@ -126,3 +129,161 @@ void VtkGrid::Write(const std::string& name) // writer->SetDataModeToBinary(); writer->Write(); } + +#else + +VtkGrid::VtkGrid(int nx, int ny, int nz, + double xmin, double xmax, + double ymin, double ymax, + double zmin, double zmax) : + ThreeDGrid(nx, ny, nz, xmin, xmax, ymin, ymax, zmin, zmax) +{ + // Set knots + coord["X"].resize(nx); + coord["Y"].resize(ny); + coord["Z"].resize(nz); + + for (int i=0; i1) { + for (int i=0; i& data, const std::string& name) +{ + // Remove XML sensitive characters; paraview bug? + std::string newName(name); + replaceAll(newName, ">", ".gt."); + replaceAll(newName, "<", ".lt."); + + dataSet[newName].resize(nx*ny*nz); + + // Insert grid data + // + int I = 0; + + for (int i=0; i(data[(k*ny + j)*nx + i]); + } + } + } + +} + + +void VtkGrid::writeBeg(std::ofstream & fout) +{ + int zero = 0; + fout << "" << std::endl; + fout << "" << std::endl; + fout << " " << std::endl; + fout << " " << std::endl; +} + +void VtkGrid::writeFields(std::ofstream & fout) +{ + fout << " " << std::endl; + for (auto v : dataSet) { + // Get ranges + auto vmin = std::min_element(v.second.begin(), v.second.end()); + auto vmax = std::max_element(v.second.begin(), v.second.end()); + fout << " " << std::endl; + int cnt = 0; + for (auto & d : v.second) { + if (cnt % 6 == 0) fout << " "; + fout << std::scientific << std::setprecision(8) << d << " "; + if (++cnt % 6 == 0) fout << std::endl; + } + if (cnt % 6) fout << std::endl; + fout << " " << std::endl; + } + fout << " " << std::endl; + + // No cell data here so this stanza is empty, but it's part of the spec + fout << " " << std::endl; + fout << " " << std::endl; +} + +void VtkGrid::writeCoords(std::ofstream & fout) +{ + fout << " " << std::endl; + for (auto & c : coord) { + fout << " " + << std::endl; + + int cnt = 0; + for (auto & v : c.second) { + if (cnt % 6 == 0) fout << " "; + fout << std::scientific << std::setprecision(8) << v << " "; + if (++cnt % 6 == 0) fout << std::endl; + } + if (cnt % 6) fout << std::endl; + fout << " " << std::endl; + } + fout << " " << std::endl; +} + +void VtkGrid::writeEnd(std::ofstream & fout) +{ + fout << " " << std::endl; + fout << " " << std::endl; + fout << "" << std::endl; +} + +void VtkGrid::Write(const std::string& name) +{ + // Create the filename with the correct extension for Paraview + std::ostringstream filename; + filename << name << ".vtr"; + + std::ofstream fout(filename.str()); + if (fout) { + + } else { + throw std::runtime_error + (std::string("VtkGrid::Write: could not open file <") + filename.str() + ">"); + } + + writeBeg(fout); // VTK XML preamble; open stanzas + writeFields(fout); // Write each data field in the dataset map + writeCoords(fout); // Write the coordinate grids + writeEnd(fout); // VTK XML finish; close open stanzas +} + +#endif diff --git a/include/DataGrid.H b/include/DataGrid.H index 99f457a8..befc1105 100644 --- a/include/DataGrid.H +++ b/include/DataGrid.H @@ -8,10 +8,7 @@ #include #include -#ifdef HAVE_VTK #include -#endif - /** This implementation of ThreeDGrid instantiates at VtkGrid if VTK is @@ -32,19 +29,22 @@ public: DataGrid(int nx, int ny, int nz, double xmin, double xmax, double ymin, double ymax, - double zmin, double zmax) + double zmin, double zmax, + std::string type="VTK") { -#ifdef HAVE_VTK - ptr = std::make_shared(nx, ny, nz, - xmin, xmax, - ymin, ymax, - zmin, zmax); -#else - ptr = std::make_shared(nx, ny, nz, + std::transform(type.begin(), type.end(), type.begin(), + [](unsigned char c){ return std::tolower(c); }); + + if (type.compare("vtk") == 0) + ptr = std::make_shared(nx, ny, nz, xmin, xmax, ymin, ymax, zmin, zmax); -#endif + else + ptr = std::make_shared(nx, ny, nz, + xmin, xmax, + ymin, ymax, + zmin, zmax); } //! Add data diff --git a/include/VtkGrid.H b/include/VtkGrid.H index 0d5e5fb2..cbd01d92 100644 --- a/include/VtkGrid.H +++ b/include/VtkGrid.H @@ -1,10 +1,15 @@ #ifndef _VtkGrid_H #define _VtkGrid_H -#include +#include +#include #include +#include #include #include +#include + +#ifdef HAVE_VTK // // VTK stuff @@ -55,6 +60,56 @@ public: void Write(const std::string& name); }; +#else + +#include + +/** + A ThreeDGrid implementation of the rectangular grid database + implement in VTK and designed for consumption by PyVTK or ParaView. + */ +class VtkGrid : public ThreeDGrid +{ +private: + // Grid coordinates + std::map> coord; + + // Dataset + std::map> dataSet; + + // Write header + void writeBeg(std::ofstream& file); + + // Write footer + void writeEnd(std::ofstream& file); + + // Write scalar fields + void writeFields(std::ofstream& file); + + // Write coordinates + void writeCoords(std::ofstream& file); + + // String replacement utility + void replaceAll(std::string& str, + const std::string& from, + const std::string& to); + +public: + //! Constructor + VtkGrid(int nx, int ny, int nz, + double xmin, double xmax, + double ymin, double ymax, + double zmin, double zmax); + + //! Add data for two-dimensional cylindrical basis + void Add(const std::vector& data, const std::string& name); + + //! Write output file + void Write(const std::string& name); +}; + +#endif + typedef std::shared_ptr VtkGridPtr; #endif From 2f2d0120af70ae544e6cd8b7cf51954d52e197ea Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Wed, 6 Nov 2024 13:04:33 -0500 Subject: [PATCH 40/80] Missing removal of 2Pi factors from PolarBasis --- src/PolarBasis.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PolarBasis.cc b/src/PolarBasis.cc index 67f2e454..876432fb 100644 --- a/src/PolarBasis.cc +++ b/src/PolarBasis.cc @@ -401,8 +401,8 @@ void * PolarBasis::determine_coefficients_thread(void * arg) { // For biorthogonal density component and normalization // - constexpr double norm0 = 2.0*M_PI * 0.5*M_2_SQRTPI/M_SQRT2; - constexpr double norm1 = 2.0*M_PI * 0.5*M_2_SQRTPI; + constexpr double norm0 = 0.5*M_2_SQRTPI/M_SQRT2; + constexpr double norm1 = 0.5*M_2_SQRTPI; double r, r2, facL=1.0, fac1, fac2, phi, mass; double xx, yy, zz; @@ -1044,8 +1044,8 @@ void PolarBasis::multistep_update(int from, int to, Component *c, int i, int id) // For biorthogonal density component and normalization // - constexpr double norm0 = 2.0*M_PI * 0.5*M_2_SQRTPI/M_SQRT2; - constexpr double norm1 = 2.0*M_PI * 0.5*M_2_SQRTPI; + constexpr double norm0 = 0.5*M_2_SQRTPI/M_SQRT2; + constexpr double norm1 = 0.5*M_2_SQRTPI; double mass = c->Mass(i) * component->Adiabatic(); From ca858f8acfcb2617925c15b73616ed5ea0f19b43 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Tue, 12 Nov 2024 15:47:08 -0500 Subject: [PATCH 41/80] Added lmaxfid to coefficient info --- exputil/EmpCylSL.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/exputil/EmpCylSL.cc b/exputil/EmpCylSL.cc index 01b94150..17c9b51e 100644 --- a/exputil/EmpCylSL.cc +++ b/exputil/EmpCylSL.cc @@ -1253,6 +1253,7 @@ YAML::Node EmpCylSL::getHeader_hdf5(const std::string& cachefile) node["mmax"] = getInt("mmax"); node["numx"] = getInt("numx"); node["numy"] = getInt("numy"); + node["lmaxfid"] = getInt("lmaxfid"); node["nmaxfid"] = getInt("nmaxfid"); node["nmax"] = getInt("nmax"); node["neven"] = getInt("neven"); From 203cadef85a2f677ae9bc503acb612442745153b Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Tue, 12 Nov 2024 15:47:36 -0500 Subject: [PATCH 42/80] Add KDtree to its own namespace and allow a NN cache for multithreaded operation --- expui/Centering.cc | 4 +- include/KDtree.H | 681 +++++++++++++++++++----------------- utils/Analysis/KDcyltest.cc | 4 +- utils/Analysis/KL_cyl.cc | 5 +- utils/Analysis/KL_sph.cc | 4 +- utils/Analysis/haloprof.cc | 4 +- utils/PhaseSpace/psp2vtu.cc | 4 +- 7 files changed, 370 insertions(+), 336 deletions(-) diff --git a/expui/Centering.cc b/expui/Centering.cc index 68515d7d..8b00b1f0 100644 --- a/expui/Centering.cc +++ b/expui/Centering.cc @@ -21,8 +21,8 @@ namespace Utility std::vector ctr(3, 0.0); - typedef point point3; - typedef kdtree tree3; + using point3 = KDtree::point; + using tree3 = KDtree::kdtree; std::vector points; diff --git a/include/KDtree.H b/include/KDtree.H index 40a845b3..79a3ee2a 100644 --- a/include/KDtree.H +++ b/include/KDtree.H @@ -6,381 +6,414 @@ #include #include -//! Wrapper class for std::map to keep a finite size -template -class Cache : public std::map +namespace KDtree { -public: - Cache(int N=1) : max_size(N) {} + //! Wrapper class for std::map to keep a finite size + template + class Cache : public std::map + { + public: + Cache(int N=1) : max_size(N) {} - void resize(int N) { max_size = N; } + void resize(int N) { max_size = N; } - void add( key_t key, value_t value ) - { - (*this)[key] = value; - if (this->size() > max_size) { - this->erase(--(*this).end()); + void add( key_t key, value_t value ) + { + (*this)[key] = value; + if (this->size() > max_size) { + this->erase(--(*this).end()); + } } - } - -private: - int max_size; -}; + + private: + int max_size; + }; -/** Class for representing a point - Coordinate_type must be a numeric type - Field (weight) is a double -*/ -template -class point -{ -public: - //! Null initializer - point() {} - - //! Constructor - point(std::array c, double m=1, - unsigned long indx=0) : coords_(c), mass_(m), indx_(indx) {} + /** Class for representing a point + Coordinate_type must be a numeric type + Field (weight) is a double + */ + template + class point + { + public: + //! Null initializer + point() {} + + //! Constructor + point(std::array c, double m=1, + unsigned long indx=0) : coords_(c), mass_(m), indx_(indx) {} - point(std::array c, - std::array v, - double m=1, unsigned long indx=0) : coords_(c), vels_(v), - mass_(m), indx_(indx) {} + point(std::array c, + std::array v, + double m=1, unsigned long indx=0) : coords_(c), vels_(v), + mass_(m), indx_(indx) {} - //! List constructor - point(std::initializer_list list, double m=1, - unsigned long indx=0) : mass_(m), indx_(indx) - { - size_t n = std::min(dimensions, list.size()); - std::copy_n(list.begin(), n, coords_.begin()); - } + //! List constructor + point(std::initializer_list list, double m=1, + unsigned long indx=0) : mass_(m), indx_(indx) + { + size_t n = std::min(dimensions, list.size()); + std::copy_n(list.begin(), n, coords_.begin()); + } + + point(std::initializer_list list, + std::initializer_list vlst, + double m=1, + unsigned long indx=0) : mass_(m), indx_(indx) + { + size_t n = std::min(dimensions, list.size()); + std::copy_n(list.begin(), n, coords_.begin()); + std::copy_n(vlst.begin(), n, vels_.begin()); + } - point(std::initializer_list list, - std::initializer_list vlst, - double m=1, - unsigned long indx=0) : mass_(m), indx_(indx) - { - size_t n = std::min(dimensions, list.size()); - std::copy_n(list.begin(), n, coords_.begin()); - std::copy_n(vlst.begin(), n, vels_.begin()); - } + //! Copy constructor + point(const point& p) + { + for (size_t n=0; n coords_; + std::array vels_; + double mass_; + unsigned long indx_; + }; - double mass() const + //! For iostream printing of points + template + std::ostream& operator<<(std::ostream& out, + const point& pt) { - return mass_; + out << '('; + for (size_t i = 0; i < dimensions; ++i) { + if (i > 0) out << ", "; + out << pt.get(i); + } + out << ')'; + return out; } - unsigned long indx() const + //! k-d tree implementation + template + class kdtree { - return indx_; - } + public: -private: - std::array coords_; - std::array vels_; - double mass_; - unsigned long indx_; -}; - -//! For iostream printing of points -template -std::ostream& operator<<(std::ostream& out, - const point& pt) -{ - out << '('; - for (size_t i = 0; i < dimensions; ++i) { - if (i > 0) out << ", "; - out << pt.get(i); - } - out << ')'; - return out; -} - -//! k-d tree implementation -template -class kdtree -{ -public: - typedef point point_type; + using point_type = point; -private: - struct node - { - node(const point_type& pt) : point_(pt), left_(nullptr), right_(nullptr) + struct node { - } + node(const point_type& pt) : point_(pt), left_(nullptr), right_(nullptr) + { + } - coordinate_type get(size_t index) const - { - return point_.get(index); - } + coordinate_type get(size_t index) const + { + return point_.get(index); + } + + double distance(const point_type& pt) const + { + return point_.distance(pt); + } + point_type point_; + node* left_; + node* right_; + }; - double distance(const point_type& pt) const - { - return point_.distance(pt); - } - point_type point_; - node* left_; - node* right_; - }; + using cache = Cache; - node* root_; + private: - Cache best_; + node* root_; - size_t visited_; - std::vector nodes_; + size_t visited_; + std::vector nodes_; - struct node_cmp - { - node_cmp(size_t index) : index_(index) + struct node_cmp { - } + node_cmp(size_t index) : index_(index) + { + } - bool operator()(const node& n1, const node& n2) const + bool operator()(const node& n1, const node& n2) const + { + return n1.point_.get(index_) < n2.point_.get(index_); + } + size_t index_; + }; + + node* make_tree(size_t begin, size_t end, size_t index) { - return n1.point_.get(index_) < n2.point_.get(index_); + if (end <= begin) return nullptr; + size_t n = begin + (end - begin)/2; + std::nth_element(&nodes_[begin], &nodes_[n], &nodes_[end], node_cmp(index)); + index = (index + 1) % dimensions; + nodes_[n].left_ = make_tree(begin, n, index); + nodes_[n].right_ = make_tree(n + 1, end, index); + return &nodes_[n]; } - size_t index_; - }; - node* make_tree(size_t begin, size_t end, size_t index) - { - if (end <= begin) return nullptr; - size_t n = begin + (end - begin)/2; - std::nth_element(&nodes_[begin], &nodes_[n], &nodes_[end], node_cmp(index)); - index = (index + 1) % dimensions; - nodes_[n].left_ = make_tree(begin, n, index); - nodes_[n].right_ = make_tree(n + 1, end, index); - return &nodes_[n]; - } - - void nearestN(node* root, const point_type& point, size_t index, int N) - { - if (root == nullptr) return; + void nearestN(node* root, const point_type& point, size_t index, int N, + cache& best_) + { + if (root == nullptr) return; - ++visited_; + ++visited_; // For diagnostic info only - double d = root->distance(point); - if (best_.size()first) { - best_.add(d, root); + double d = root->distance(point); + if (best_.size()first) { + best_.add(d, root); + } + + // This is only correct if the test point is never in the data set . . . + // if (best_.begin()->first == 0) return; + + double dx = root->get(index) - point.get(index); + index = (index + 1) % dimensions; + + nearestN(dx > 0 ? root->left_ : root->right_, point, index, N, best_); + + if (best_.size()>=N and dx * dx >= best_.rbegin()->first) return; + nearestN(dx > 0 ? root->right_ : root->left_, point, index, N, best_); + } + + void accum(node* root, std::vector>& ret, unsigned cur) + { + ret[cur].push_back(root->point_.indx()); + if (root->left_) accum(root->left_, ret, cur); + if (root->right_) accum(root->right_, ret, cur); } - // This is only correct if the test point is never in the data set . . . - // if (best_.begin()->first == 0) return; + void walk(node* root, std::vector>& ret, + unsigned int cur, unsigned int lev, unsigned int level) + { + if (lev == level) accum(root, ret, cur); + else { + ret[cur].push_back(root->point_.indx()); + walk(root->left_, ret, 2*cur + 0, lev + 1, level); + walk(root->right_, ret, 2*cur + 1, lev + 1, level); + } + } - double dx = root->get(index) - point.get(index); - index = (index + 1) % dimensions; - nearestN(dx > 0 ? root->left_ : root->right_, point, index, N); + public: - if (best_.size()>=N and dx * dx >= best_.rbegin()->first) return; - nearestN(dx > 0 ? root->right_ : root->left_, point, index, N); - } - -public: - //@{ - //! Copy constructor2 - kdtree(const kdtree&) = delete; - kdtree& operator=(const kdtree&) = delete; - //@} - - /** - * Constructor taking a pair of iterators. Adds each - * point in the range [begin, end) to the tree. - * - * @param begin start of range - * @param end end of range - */ - template - kdtree(iterator begin, iterator end) - { - best_.resize(1); - visited_ = 0; - nodes_.reserve(std::distance(begin, end)); - for (auto i = begin; i != end; ++i) - nodes_.emplace_back(*i); - root_ = make_tree(0, nodes_.size(), 0); - } - - /** - * Constructor taking a function object that generates - * points. The function object will be called n times - * to populate the tree. - * - * @param f function that returns a point - * @param n number of points to add - */ - template - kdtree(func&& f, size_t n) - { - best_.resize(1); - visited_ = 0; - nodes_.reserve(n); - for (size_t i = 0; i < n; ++i) - nodes_.emplace_back(f()); - root_ = make_tree(0, nodes_.size(), 0); - } + //@{ + //! Copy constructor2 + kdtree(const kdtree&) = delete; + kdtree& operator=(const kdtree&) = delete; + //@} - /** - * Returns true if the tree is empty, false otherwise. - */ - bool empty() const - { - return nodes_.empty(); - } - - /** - * Returns the number of nodes visited by the last call - * to nearest(). - */ - size_t visited() const - { - return visited_; - } - - /** - * Returns the distance between the input point and return value - * from the last call to nearest(). - */ - double distance() const - { - return std::sqrt(best_.begin()->first); - } + /** + * Constructor taking a pair of iterators. Adds each + * point in the range [begin, end) to the tree. + * + * @param begin start of range + * @param end end of range + */ + template + kdtree(iterator begin, iterator end) + { + visited_ = 0; + nodes_.reserve(std::distance(begin, end)); + for (auto i = begin; i != end; ++i) + nodes_.emplace_back(*i); + root_ = make_tree(0, nodes_.size(), 0); + } + + /** + * Constructor taking a function object that generates + * points. The function object will be called n times + * to populate the tree. + * + * @param f function that returns a point + * @param n number of points to add + */ + template + kdtree(func&& f, size_t n) + { + visited_ = 0; + nodes_.reserve(n); + for (size_t i = 0; i < n; ++i) + nodes_.emplace_back(f()); + root_ = make_tree(0, nodes_.size(), 0); + } + + /** + * Returns true if the tree is empty, false otherwise. + */ + bool empty() const + { + return nodes_.empty(); + } - /** - * Finds the nearest N points in the tree to the given point. It is - * not valid to call this function if the tree is empty. - * - * @param pt a point - * @param N is the number of nearest points - * - * Returns: tuple of the first points, summed weight, and the radius of the Nth - * point - */ - std::tuple - nearestN(const point_type& pt, int N) - { - if (root_ == nullptr) throw std::logic_error("tree is empty"); - best_.clear(); - best_.resize(N); - visited_ = 0; - nearestN(root_, pt, 0, N); + /** + * Returns the number of nodes visited by the last call + * to nearest(). + */ + size_t visited() const + { + return visited_; + } + + /** + * Returns the distance between the input point and return value + * from the last call to nearest(). + */ + double distance(cache& best_) const + { + return std::sqrt(best_.begin()->first); + } + + /** + * Finds the nearest N points in the tree to the given point. It is + * not valid to call this function if the tree is empty. + * + * @param pt a point + * @param N is the number of nearest points + * + * Returns: tuple of the first points, summed weight, and the radius of the Nth + * point + */ + std::tuple + nearestN(const point_type& pt, int N) + { + if (root_ == nullptr) throw std::logic_error("tree is empty"); + cache best_(N); + visited_ = 0; + nearestN(root_, pt, 0, N, best_); - double wgt = 0.0; // Sum weights - for (auto b : best_) wgt += b.second->point_.mass(); + double wgt = 0.0; // Sum weights + for (auto b : best_) wgt += b.second->point_.mass(); #if __GNUC__ > 6 - return {best_.begin()->second->point_, wgt, std::sqrt(best_.rbegin()->first)}; + return {best_.begin()->second->point_, wgt, std::sqrt(best_.rbegin()->first), best_}; #else - std::tuple ret; - std::get<0>(ret) = best_.begin()->second->point_; - std::get<1>(ret) = wgt; - std::get<2>(ret) = std::sqrt(best_.rbegin()->first); - return ret; + std::tuple ret; + std::get<0>(ret) = best_.begin()->second->point_; + std::get<1>(ret) = wgt; + std::get<2>(ret) = std::sqrt(best_.rbegin()->first); + std::get<3>(ret) = best_; + return ret; #endif - } - - /** - * Finds the nearest N points in the tree to the given point. It is - * not valid to call this function if the tree is empty. - * - * @param pt a point - * @param N is the number of nearest points + } - * Returns: tuple of the nearest point list and the radius of the - * Nth point - */ - std::tuple, double> - nearestList(const point_type& pt, int N) - { - if (root_ == nullptr) throw std::logic_error("tree is empty"); - best_.clear(); - best_.resize(N); - visited_ = 0; - nearestN(root_, pt, 0, N); + /** + * Finds the nearest N points in the tree to the given point. It is + * not valid to call this function if the tree is empty. + * + * @param pt a point + * @param N is the number of nearest points + + * Returns: tuple of the nearest point list and the radius of the + * Nth point + */ + std::tuple, double, cache> + nearestList(const point_type& pt, int N) + { + if (root_ == nullptr) throw std::logic_error("tree is empty"); + cache best_(N); + visited_ = 0; + nearestN(root_, pt, 0, N, best_); - std::vector pts; // The returned point list - for (auto b : best_) pts.push_back(b.second->point_); + std::vector pts; // The returned point list + for (auto b : best_) pts.push_back(b.second->point_); #if __GNUC__ > 6 - return {pts, std::sqrt(best_.rbegin()->first)}; + return {pts, std::sqrt(best_.rbegin()->first), best_}; #else - std::tuple, double> ret; - std::get<0>(ret) = pts; - std::get<1>(ret) = std::sqrt(best_.rbegin()->first); - return ret; + std::tuple, double> ret; + std::get<0>(ret) = pts; + std::get<1>(ret) = std::sqrt(best_.rbegin()->first); + std::get<2>(ret) = best_; + return ret; #endif - } + } - std::vector getDist() - { - std::vector ret; - for (auto v : best_) ret.push_back(v.first); - return ret; - } + std::vector getDist(cache& best_) + { + std::vector ret; + for (auto v : best_) ret.push_back(v.first); + return ret; + } + + std::vector> getPartition(unsigned level) + { + int partitions = (1 << level); + std::vector> ret(partitions); + walk(root_, ret, 0, 0, level); + return ret; + } -}; + }; +} +// END: namespace KDtree diff --git a/utils/Analysis/KDcyltest.cc b/utils/Analysis/KDcyltest.cc index b272ccdb..edf29c8b 100644 --- a/utils/Analysis/KDcyltest.cc +++ b/utils/Analysis/KDcyltest.cc @@ -343,8 +343,8 @@ main(int argc, char **argv) // This is the kd- NN density estimate; skipped by default for Ndens=0 // if (Ndens<=0) Ndens = 2; - typedef point point3; - typedef kdtree tree3; + using point3 = KDtree::point; + using tree3 = KDtree::kdtree; std::vector points; diff --git a/utils/Analysis/KL_cyl.cc b/utils/Analysis/KL_cyl.cc index c77a9416..ec7f4755 100644 --- a/utils/Analysis/KL_cyl.cc +++ b/utils/Analysis/KL_cyl.cc @@ -436,11 +436,12 @@ main(int argc, char **argv) // This is the kd- NN density estimate; skipped by default for Ndens=0 // if (Ndens) { + if (myid==0) std::cout << "Computing KD density estimate for " << nbod << " points" << std::endl; - typedef point point3; - typedef kdtree tree3; + using point3 = KDtree::point; + using tree3 = KDtree::kdtree; std::vector points; diff --git a/utils/Analysis/KL_sph.cc b/utils/Analysis/KL_sph.cc index 7c1a385c..a9d20583 100644 --- a/utils/Analysis/KL_sph.cc +++ b/utils/Analysis/KL_sph.cc @@ -302,8 +302,8 @@ main(int argc, char **argv) if (myid==0) std::cout << "Computing KD density estimate for " << nbod << " points" << std::endl; - typedef point point3; - typedef kdtree tree3; + using point3 = KDtree::point ; + using tree3 = KDtree::kdtree; std::vector points; diff --git a/utils/Analysis/haloprof.cc b/utils/Analysis/haloprof.cc index 8e3c69b9..5fcddab9 100644 --- a/utils/Analysis/haloprof.cc +++ b/utils/Analysis/haloprof.cc @@ -811,8 +811,8 @@ main(int argc, char **argv) // Density weight COM // if (KD) { - typedef point point3; - typedef kdtree tree3; + using point3 = KDtree::point ; + using tree3 = KDtree::kdtree; std::vector points; diff --git a/utils/PhaseSpace/psp2vtu.cc b/utils/PhaseSpace/psp2vtu.cc index 1d780033..45a8f94d 100644 --- a/utils/PhaseSpace/psp2vtu.cc +++ b/utils/PhaseSpace/psp2vtu.cc @@ -215,8 +215,8 @@ main(int ac, char **av) dens->SetName("density"); if (Ndens) { - typedef point point3; - typedef kdtree tree3; + using point3 = KDtree::point ; + using tree3 = KDtree::kdtree; std::vector mass; std::vector points; From 6817c594f25ebf9b5b9f605bfdf5551920dd55ec Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Fri, 15 Nov 2024 11:03:56 -0500 Subject: [PATCH 43/80] Update to normalization scheme for 2-d bases --- src/PolarBasis.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/PolarBasis.cc b/src/PolarBasis.cc index 876432fb..a28edfe8 100644 --- a/src/PolarBasis.cc +++ b/src/PolarBasis.cc @@ -401,8 +401,8 @@ void * PolarBasis::determine_coefficients_thread(void * arg) { // For biorthogonal density component and normalization // - constexpr double norm0 = 0.5*M_2_SQRTPI/M_SQRT2; - constexpr double norm1 = 0.5*M_2_SQRTPI; + constexpr double norm0 = 1.0; + constexpr double norm1 = M_SQRT2; double r, r2, facL=1.0, fac1, fac2, phi, mass; double xx, yy, zz; @@ -1044,8 +1044,8 @@ void PolarBasis::multistep_update(int from, int to, Component *c, int i, int id) // For biorthogonal density component and normalization // - constexpr double norm0 = 0.5*M_2_SQRTPI/M_SQRT2; - constexpr double norm1 = 0.5*M_2_SQRTPI; + constexpr double norm0 = 1.0; + constexpr double norm1 = M_SQRT2; double mass = c->Mass(i) * component->Adiabatic(); @@ -1323,8 +1323,8 @@ void PolarBasis::compute_multistep_coefficients() void * PolarBasis::determine_acceleration_and_potential_thread(void * arg) { - constexpr double norm0 = 0.5*M_2_SQRTPI/M_SQRT2; - constexpr double norm1 = 0.5*M_2_SQRTPI; + constexpr double norm0 = 1.0; + constexpr double norm1 = M_SQRT2; double r, r0=0.0, phi; double potr, potz, potl, potp, p, pc, drc, drs, dzc, dzs, ps, dfacp, facdp; From c245d79daa98e1a168bf842df654ce6febb75fe9 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Fri, 15 Nov 2024 11:51:25 -0500 Subject: [PATCH 44/80] Fix backward compatibility for old GNU and except Clang --- include/KDtree.H | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/KDtree.H b/include/KDtree.H index 79a3ee2a..1bce7122 100644 --- a/include/KDtree.H +++ b/include/KDtree.H @@ -354,10 +354,10 @@ namespace KDtree double wgt = 0.0; // Sum weights for (auto b : best_) wgt += b.second->point_.mass(); -#if __GNUC__ > 6 +#if __GNUC__ > 6 or defined(__clang__) return {best_.begin()->second->point_, wgt, std::sqrt(best_.rbegin()->first), best_}; #else - std::tuple ret; + std::tuple ret; std::get<0>(ret) = best_.begin()->second->point_; std::get<1>(ret) = wgt; std::get<2>(ret) = std::sqrt(best_.rbegin()->first); From 6baf465c8651c24eaeeb66b34c04ce5848cd2b02 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sun, 17 Nov 2024 22:02:31 -0500 Subject: [PATCH 45/80] Add model tag --- exputil/EmpCyl2d.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/exputil/EmpCyl2d.cc b/exputil/EmpCyl2d.cc index 16a419f0..7145287f 100644 --- a/exputil/EmpCyl2d.cc +++ b/exputil/EmpCyl2d.cc @@ -769,6 +769,7 @@ EmpCyl2d::EmpCyl2d if (cache_name_2d.size()==0) cache_name_2d = default_cache_name; disk = createModel(); + model = disk->ID(); basis = Basis2d::createBasis(mmax, nmaxfid, rmax, biorth); basis_test = false; From f9a0f2087b8d11c9081b126db3b4f2cca0babddb Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 18 Nov 2024 22:23:20 -0500 Subject: [PATCH 46/80] Separate background from target models in PolarModel --- exputil/BiorthCyl.cc | 35 +++++ exputil/EmpCyl2d.cc | 289 -------------------------------------- include/BiorthCyl.H | 15 +- include/EmpCyl2d.H | 320 ++++++++++++++++++++++++++++++++++++++++--- src/Cylinder.cc | 5 +- src/FlatDisk.H | 16 ++- src/FlatDisk.cc | 99 ++++++++++++- utils/SL/EOF2d.cc | 4 +- 8 files changed, 454 insertions(+), 329 deletions(-) diff --git a/exputil/BiorthCyl.cc b/exputil/BiorthCyl.cc index 3bae390a..d323b4c5 100644 --- a/exputil/BiorthCyl.cc +++ b/exputil/BiorthCyl.cc @@ -836,3 +836,38 @@ std::vector BiorthCyl::orthoCheck() return emp.orthoCheck(); } + + +void BiorthCyl::dump_basis(const string& name) +{ + const int num = 1000; + double rmin = emp.getRmin(); + double rmax = emp.getRmax(); + double r, dr = rmax/(num-1); + + std::ofstream out; + + for (int m=0; m<=mmax; m++) { + + std::ostringstream ins; + ins << name << ".biorthcyl." << m; + out.open(ins.str().c_str()); + + // Ok, write data + + for (int j=0; j #include -#include #include -#include // Set to true for orthogonality checking // @@ -331,293 +329,6 @@ EmpCyl2d::Basis2d::createBasis(int mmax, int nmax, double rmax, return std::make_shared(); } -class EmpCyl2d::ExponCyl : public EmpCyl2d::ModelCyl -{ - -private: - - double acyl, sigma0; - - // Assign values from YAML - // - void parse(const YAML::Node& conf) - { - try { - if (conf["acyl"]) - acyl = conf["acyl"].as(); - else - acyl = 1.0; - } - catch (YAML::Exception & error) { - if (myid==0) - std::cout << "Error parsing parameters in EmpCyl2d::ExponCyl: " - << error.what() << std::endl - << std::string(60, '-') << std::endl - << "Config node" << std::endl - << std::string(60, '-') << std::endl - << conf << std::endl - << std::string(60, '-') << std::endl; - throw std::runtime_error("EmpCyl2d::ExponCyl: error parsing YAML config"); - } - } - -public: - - ExponCyl(const YAML::Node& par) - { - parse(par); - sigma0 = 0.5/(M_PI*acyl*acyl); - id = "expon"; - } - - double pot(double r) { - double y = 0.5 * r / acyl; - return -M_PI*sigma0*r * - (EXPmath::cyl_bessel_i(0, y)*EXPmath::cyl_bessel_k(1, y) - - EXPmath::cyl_bessel_i(1, y)*EXPmath::cyl_bessel_k(0, y)); - } - - double dpot(double r) { - double y = 0.5 * r / acyl; - return 2.0*M_PI*sigma0*y* - (EXPmath::cyl_bessel_i(0, y)*EXPmath::cyl_bessel_k(0, y) - - EXPmath::cyl_bessel_i(1, y)*EXPmath::cyl_bessel_k(1, y)); - } - - double dens(double r) { - return sigma0*exp(-r/acyl); - } - -}; - -class EmpCyl2d::KuzminCyl : public EmpCyl2d::ModelCyl -{ -private: - - double acyl; - - // Assign values from YAML - // - void parse(const YAML::Node& conf) - { - try { - if (conf["acyl"]) - acyl = conf["acyl"].as(); - else - acyl = 1.0; - } - catch (YAML::Exception & error) { - if (myid==0) - std::cout << "Error parsing parameters in EmpCyl2d::KuzminCyl: " - << error.what() << std::endl - << std::string(60, '-') << std::endl - << "Config node" << std::endl - << std::string(60, '-') << std::endl - << conf << std::endl - << std::string(60, '-') << std::endl; - throw std::runtime_error("EmpCyl2d::KuzminCyl: error parsing YAML config"); - } - } - -public: - - KuzminCyl(const YAML::Node& par) - { - parse(par); - id = "kuzmin"; - } - - double pot(double R) { - double a2 = acyl * acyl; - return -1.0/sqrt(R*R + a2); - } - - double dpot(double R) { - double a2 = acyl * acyl; - return R/pow(R*R + a2, 1.5); - } - - double dens(double R) { - double a2 = acyl * acyl; - return 4.0*M_PI*acyl/pow(R*R + a2, 1.5)/(2.0*M_PI); - // ^ - // | - // This 4pi from Poisson's eqn - } - -}; - - -class EmpCyl2d::MestelCyl : public EmpCyl2d::ModelCyl -{ -protected: - - double vrot, rot; - - // Assign values from YAML - // - void parse(const YAML::Node& conf) - { - try { - if (conf["vrot"]) - vrot = conf["vrot"].as(); - else - vrot = 1.0; - } - catch (YAML::Exception & error) { - if (myid==0) - std::cout << "Error parsing parameters in EmpCyl2d::MestelCyl: " - << error.what() << std::endl - << std::string(60, '-') << std::endl - << "Config node" << std::endl - << std::string(60, '-') << std::endl - << conf << std::endl - << std::string(60, '-') << std::endl; - throw std::runtime_error("EmpCyl2d::MestelCyl: error parsing YAML config"); - } - } - -public: - - MestelCyl(const YAML::Node& par) - { - parse(par); - rot = vrot*vrot; - id = "mestel"; - } - - virtual double pot(double R) { - if (R>0.0) return rot*log(R); - else throw std::runtime_error("MestelCyl::pot: R<=0"); - } - - virtual double dpot(double R) { - if (R>0.0) return rot/R; - else throw std::runtime_error("MestelCyl::dpot: R<=0"); - } - - virtual double dens(double R) { - if (R>0.0) return rot/(2.0*M_PI*R); - else throw std::runtime_error("MestelCyl::dens: R<=0"); - } -}; - - -class EmpCyl2d::ZangCyl : public EmpCyl2d::MestelCyl -{ - -private: - //! Parameters - double mu, nu, ri, ro; - - //! Softening factor (not currently used) - double asoft = 1.0e-8; - - //! Ignore inner cut-off for N<0.05 - bool Inner = true; - - //! Taper factors - double Tifac, Tofac; - - //! Inner taper function - double Tinner(double Jp) - { - double fac = pow(Jp, nu); - return fac/(Tifac + fac); - } - - //! Outer taper function - double Touter(double Jp) - { - return 1.0/(1.0 + pow(Jp/Tofac, mu)); - } - - //! Deriv of inner taper function - double dTinner(double Jp) - { - double fac = pow(Jp, nu); - double fac2 = Tifac + fac; - return nu*fac/Jp/(fac2*fac2); - } - - //! Deriv of outer taper function - double dTouter(double Jp) - { - double fac = pow(Jp/Tofac, mu); - double fac2 = 1.0 + fac; - return -mu*fac/Jp/(fac2*fac2); - } - -protected: - - //! Assign values from YAML - void parse(const YAML::Node& conf) - { - try { - if (conf["Ninner"]) - nu = conf["Ninner"].as(); - else - nu = 2.0; - - if (conf["Mouter"]) - mu = conf["Mouter"].as(); - else - mu = 2.0; - - if (conf["Ri"]) - ri = conf["Ri"].as(); - else - ri = 1.0; - - if (conf["Ro"]) - ro = conf["Ro"].as(); - else - ro = 10.0; - } - catch (YAML::Exception & error) { - if (myid==0) - std::cout << "Error parsing parameters in EmpCyl2d::ZangCyl: " - << error.what() << std::endl - << std::string(60, '-') << std::endl - << "Config node" << std::endl - << std::string(60, '-') << std::endl - << conf << std::endl - << std::string(60, '-') << std::endl; - throw std::runtime_error("EmpCyl2d::ZangCyl: error parsing YAML config"); - } - } - -public: - - //! Constructor - ZangCyl(const YAML::Node& par) : MestelCyl(par) - { - // Parse the YAML - parse(par); - - // Assign the id - id = "zang"; - - // Cache taper factors - Tifac = pow(ri*vrot, nu); - Tofac = ro*vrot; - - if (nu<0.05) { - // Exponent is now for mapping only - Inner = false; - } - } - - //! Surface density - double dens(double R) - { - double ret = MestelCyl::dens(R) * Touter(R); - if (Inner) ret *= Tinner(R); - return ret; - } - -}; - std::shared_ptr EmpCyl2d::createModel() { try { diff --git a/include/BiorthCyl.H b/include/BiorthCyl.H index b052c797..d29d288a 100644 --- a/include/BiorthCyl.H +++ b/include/BiorthCyl.H @@ -201,13 +201,6 @@ public: //! Evaluate all orders in matrices; for n-body void get_pot(Eigen::MatrixXd& Vc, Eigen::MatrixXd& Vs, double r, double z); - //! Background evaluation - virtual std::tuple background(double r, double z) - { - auto [p, dr, d] = emp.background(r); - return {p, dr, 0.0}; - } - //! Get the table bounds double getRtable() { return rcylmax*scale; } @@ -220,6 +213,14 @@ public: double getYmin() { return ymin; } double getYmax() { return ymax; } + //! Dump the basis for plotting + void dump_basis(const string& name); + + //! Get model name + const std::string getModelName() { return emp.getModelName(); } + + //! Get biorthogonal function base name + const std::string getBiorthName() { return emp.getBiorthName(); } }; #endif diff --git a/include/EmpCyl2d.H b/include/EmpCyl2d.H index 703e8e61..7a5ca6bb 100644 --- a/include/EmpCyl2d.H +++ b/include/EmpCyl2d.H @@ -10,11 +10,14 @@ #include #include -#include #include // Parameters parsing #include // For config macros +#include +#include +#include + // For reading and writing cache file // #include @@ -69,6 +72,295 @@ public: double d_xi_to_r(double xi); }; + + class ExponCyl : public ModelCyl + { + + private: + + double acyl, sigma0; + + // Assign values from YAML + // + void parse(const YAML::Node& conf) + { + try { + if (conf["acyl"]) + acyl = conf["acyl"].as(); + else + acyl = 1.0; + } + catch (YAML::Exception & error) { + if (myid==0) + std::cout << "Error parsing parameters in EmpCyl2d::ExponCyl: " + << error.what() << std::endl + << std::string(60, '-') << std::endl + << "Config node" << std::endl + << std::string(60, '-') << std::endl + << conf << std::endl + << std::string(60, '-') << std::endl; + throw std::runtime_error("EmpCyl2d::ExponCyl: error parsing YAML config"); + } + } + + public: + + ExponCyl(const YAML::Node& par) + { + parse(par); + sigma0 = 0.5/(M_PI*acyl*acyl); + id = "expon"; + } + + double pot(double r) { + double y = 0.5 * r / acyl; + return -M_PI*sigma0*r * + (EXPmath::cyl_bessel_i(0, y)*EXPmath::cyl_bessel_k(1, y) - + EXPmath::cyl_bessel_i(1, y)*EXPmath::cyl_bessel_k(0, y)); + } + + double dpot(double r) { + double y = 0.5 * r / acyl; + return 2.0*M_PI*sigma0*y* + (EXPmath::cyl_bessel_i(0, y)*EXPmath::cyl_bessel_k(0, y) - + EXPmath::cyl_bessel_i(1, y)*EXPmath::cyl_bessel_k(1, y)); + } + + double dens(double r) { + return sigma0*exp(-r/acyl); + } + + }; + + class KuzminCyl : public ModelCyl + { + private: + + double acyl; + + // Assign values from YAML + // + void parse(const YAML::Node& conf) + { + try { + if (conf["acyl"]) + acyl = conf["acyl"].as(); + else + acyl = 1.0; + } + catch (YAML::Exception & error) { + if (myid==0) + std::cout << "Error parsing parameters in EmpCyl2d::KuzminCyl: " + << error.what() << std::endl + << std::string(60, '-') << std::endl + << "Config node" << std::endl + << std::string(60, '-') << std::endl + << conf << std::endl + << std::string(60, '-') << std::endl; + throw std::runtime_error("EmpCyl2d::KuzminCyl: error parsing YAML config"); + } + } + + public: + + KuzminCyl(const YAML::Node& par) + { + parse(par); + id = "kuzmin"; + } + + double pot(double R) { + double a2 = acyl * acyl; + return -1.0/sqrt(R*R + a2); + } + + double dpot(double R) { + double a2 = acyl * acyl; + return R/pow(R*R + a2, 1.5); + } + + double dens(double R) { + double a2 = acyl * acyl; + return 4.0*M_PI*acyl/pow(R*R + a2, 1.5)/(2.0*M_PI); + // ^ + // | + // This 4pi from Poisson's eqn + } + + }; + + + class MestelCyl : public ModelCyl + { + protected: + + double vrot, rot; + + // Assign values from YAML + // + void parse(const YAML::Node& conf) + { + try { + if (conf["vrot"]) + vrot = conf["vrot"].as(); + else + vrot = 1.0; + } + catch (YAML::Exception & error) { + if (myid==0) + std::cout << "Error parsing parameters in EmpCyl2d::MestelCyl: " + << error.what() << std::endl + << std::string(60, '-') << std::endl + << "Config node" << std::endl + << std::string(60, '-') << std::endl + << conf << std::endl + << std::string(60, '-') << std::endl; + throw std::runtime_error("EmpCyl2d::MestelCyl: error parsing YAML config"); + } + } + + public: + + MestelCyl(const YAML::Node& par) + { + parse(par); + rot = vrot*vrot; + id = "mestel"; + } + + virtual double pot(double R) { + if (R>0.0) return rot*log(R); + else throw std::runtime_error("MestelCyl::pot: R<=0"); + } + + virtual double dpot(double R) { + if (R>0.0) return rot/R; + else throw std::runtime_error("MestelCyl::dpot: R<=0"); + } + + virtual double dens(double R) { + if (R>0.0) return rot/(2.0*M_PI*R); + else throw std::runtime_error("MestelCyl::dens: R<=0"); + } + }; + + + class ZangCyl : public MestelCyl + { + + private: + //! Parameters + double mu, nu, ri, ro; + + //! Softening factor (not currently used) + double asoft = 1.0e-8; + + //! Ignore inner cut-off for N<0.05 + bool Inner = true; + + //! Taper factors + double Tifac, Tofac; + + //! Inner taper function + double Tinner(double Jp) + { + double fac = pow(Jp, nu); + return fac/(Tifac + fac); + } + + //! Outer taper function + double Touter(double Jp) + { + return 1.0/(1.0 + pow(Jp/Tofac, mu)); + } + + //! Deriv of inner taper function + double dTinner(double Jp) + { + double fac = pow(Jp, nu); + double fac2 = Tifac + fac; + return nu*fac/Jp/(fac2*fac2); + } + + //! Deriv of outer taper function + double dTouter(double Jp) + { + double fac = pow(Jp/Tofac, mu); + double fac2 = 1.0 + fac; + return -mu*fac/Jp/(fac2*fac2); + } + + protected: + + //! Assign values from YAML + void parse(const YAML::Node& conf) + { + try { + if (conf["Ninner"]) + nu = conf["Ninner"].as(); + else + nu = 2.0; + + if (conf["Mouter"]) + mu = conf["Mouter"].as(); + else + mu = 2.0; + + if (conf["Ri"]) + ri = conf["Ri"].as(); + else + ri = 1.0; + + if (conf["Ro"]) + ro = conf["Ro"].as(); + else + ro = 10.0; + } + catch (YAML::Exception & error) { + if (myid==0) + std::cout << "Error parsing parameters in EmpCyl2d::ZangCyl: " + << error.what() << std::endl + << std::string(60, '-') << std::endl + << "Config node" << std::endl + << std::string(60, '-') << std::endl + << conf << std::endl + << std::string(60, '-') << std::endl; + throw std::runtime_error("EmpCyl2d::ZangCyl: error parsing YAML config"); + } + } + + public: + + //! Constructor + ZangCyl(const YAML::Node& par) : MestelCyl(par) + { + // Parse the YAML + parse(par); + + // Assign the id + id = "zang"; + + // Cache taper factors + Tifac = pow(ri*vrot, nu); + Tofac = ro*vrot; + + if (nu<0.05) { + // Exponent is now for mapping only + Inner = false; + } + } + + //! Surface density + double dens(double R) + { + double ret = MestelCyl::dens(R) * Touter(R); + if (Inner) ret *= Tinner(R); + return ret; + } + + }; + + protected: //! Contains parameter database @@ -113,11 +405,6 @@ protected: std::shared_ptr createModel(); - class ExponCyl; - class MestelCyl; - class KuzminCyl; - class ZangCyl; - //! Mapping instance Mapping map; @@ -206,22 +493,15 @@ public: //! Check for configuration bool operator()() { return configured; } - //@{ - //! Background evaluation - void background(double r, double & p, double & dr) - { - p = disk->pot(r); - dr = disk->dpot(r); - } - - std::tuple background(double r) - { - return {disk->pot(r), disk->dpot(r), disk->dens(r)}; - } - //@} - //! Get coordinate mapping object Mapping getMapping() { return map; } + + //! Get model name + const std::string getModelName() { return model; } + + //! Get biorthogonal function base name + const std::string getBiorthName() { return biorth; } + }; diff --git a/src/Cylinder.cc b/src/Cylinder.cc index e355bcdf..73426b89 100644 --- a/src/Cylinder.cc +++ b/src/Cylinder.cc @@ -106,7 +106,8 @@ Cylinder::valid_keys = { "self_consistent", "playback", "coefCompute", - "coefMaster" + "coefMaster", + "dump" }; Cylinder::Cylinder(Component* c0, const YAML::Node& conf, MixtureBasis *m) : @@ -552,6 +553,8 @@ void Cylinder::initialize() } } + if (conf["dump"]) dump_basis = true; + } catch (YAML::Exception & error) { if (myid==0) std::cout << "Error parsing Cylinder parameters: " diff --git a/src/FlatDisk.H b/src/FlatDisk.H index 64be763a..3eecc2bc 100644 --- a/src/FlatDisk.H +++ b/src/FlatDisk.H @@ -74,14 +74,18 @@ private: } #endif + //! Background instance + using Disk2d = std::shared_ptr; + Disk2d disk; + + //! Set background from YAML + void setBackground(); + //! Background evaluation virtual std::tuple get_pot_background(double r, double z) - { - auto [p, dr, dz] = ortho->background(r, z); - return {p, -dr, -dz}; - } - + { return {disk->pot(r), -disk->dpot(r), 0.0}; } + void get_dens(double r, double z, Eigen::MatrixXd& p, int tid); void get_potl_dens(double r, double z, @@ -99,6 +103,8 @@ private: //! Valid keys for YAML configurations static const std::set valid_keys; + bool dump_basis; + protected: //! Use BiorthCyl to evaluate the basis at r, z diff --git a/src/FlatDisk.cc b/src/FlatDisk.cc index faf626fa..8ac4ab0c 100644 --- a/src/FlatDisk.cc +++ b/src/FlatDisk.cc @@ -21,25 +21,33 @@ FlatDisk::valid_keys = { "model", "biorth", "diskconf", - "cachename" + "background", + "cachename", + "dump" }; FlatDisk::FlatDisk(Component* c0, const YAML::Node& conf, MixtureBasis* m) : PolarBasis(c0, conf, m) { id = "FlatDisk EOF"; - // Defaults + + // Defaults + // knots = 40; numr = 2000; rcylmin = 0.0; rcylmax = 10.0; logr = false; is_flat = true; + dump_basis = false; - // Get initialization info + // Get initialization info + // initialize(); - id += "[" + model + "][" + biorth + "]"; + // Append info to basis ID + // + id += "[" + ortho->getModelName() + "][" + ortho->getBiorthName() + "]"; if (myid==0) { std::string sep("---- "); @@ -81,6 +89,8 @@ void FlatDisk::initialize() if (conf["biorth"]) biorth = conf["biorth"].as(); if (conf["mmax"]) Lmax = Mmax = mmax; // Override base-class values + + if (conf["dump"]) dump_basis = conf["dump"].as(); } catch (YAML::Exception & error) { if (myid==0) std::cout << "Error parsing parameters in FlatDisk: " @@ -96,6 +106,10 @@ void FlatDisk::initialize() // Create the BiorthCyl instance ortho = std::make_shared(conf); + if (dump_basis) ortho->dump_basis(runtag); + + // Set background model + if (conf["background"]) setBackground(); } FlatDisk::~FlatDisk(void) @@ -104,6 +118,83 @@ FlatDisk::~FlatDisk(void) } +void FlatDisk::setBackground() +{ + try { + + YAML::Node Params = conf["background"]; + + std::string name = Params["name"].as(); + auto params = Params["parameters"]; + + // Convert ID string to lower case + // + std::transform(name.begin(), name.end(), name.begin(), + [](unsigned char c){ return std::tolower(c); }); + + if (name.find("kuzmin") != std::string::npos) { + if (myid==0) { + std::cout << "---- FlatDisk: Making a Kuzmin disk"; + if (params) std::cout << " with" << std::endl << Params["parameters"]; + std::cout << std::endl; + } + disk = std::make_shared(params); + return; + } + + if (name.find("mestel") != std::string::npos) { + if (myid==0) { + std::cout << "---- FlatDisk: Making a finite Mestel disk"; + if (params) std::cout << " with" << std::endl << Params["parameters"]; + std::cout << std::endl; + } + disk = std::make_shared(params); + return; + } + + if (name.find("zang") != std::string::npos) { + if (myid==0) { + std::cout << "---- FlatDisk: Making a double-tapered Zang"; + YAML::Emitter out; + out << YAML::Flow << params; + if (params) std::cout << " with " << out.c_str(); + std::cout << std::endl; + } + disk = std::make_shared(params); + return; + } + + if (name.find("expon") != std::string::npos) { + if (myid==0) { + std::cout << "---- FlatDisk: Making an Exponential disk"; + if (params) std::cout << " with" << std::endl << params; + std::cout << std::endl; + } + disk = std::make_shared(params); + return; + } + + // Default if nothing else matches + if (myid==0) + std::cout << "---- FlatDisk: Making an Exponential disk [Default]" + << std::endl; + disk = std::make_shared(params); + return; + } + catch (YAML::Exception & error) { + if (myid==0) + std::cout << "Error parsing parameters in FlatDisk::setBackground: " + << error.what() << std::endl + << std::string(60, '-') << std::endl + << "Config node" << std::endl + << std::string(60, '-') << std::endl + << conf["background"] << std::endl + << std::string(60, '-') << std::endl; + throw std::runtime_error("FlatDisk::setBackground: error parsing YAML config"); + } +} + + void FlatDisk::get_dpotl(double r, double z, Eigen::MatrixXd& p, Eigen::MatrixXd& dpr, diff --git a/utils/SL/EOF2d.cc b/utils/SL/EOF2d.cc index e2aeceb8..3cb8087b 100644 --- a/utils/SL/EOF2d.cc +++ b/utils/SL/EOF2d.cc @@ -423,11 +423,9 @@ int main(int argc, char** argv) sum1 += emp.get_potl(r, 2, n)*coef1[n]; sum2 += emp.get_potl(r, 2, n)*coef2[n]; } - auto [p, dp, d] = emp.background(r); + std::cout << std::setw(16) << r << std::setw(16) << pot(r) - << std::setw(16) << p - << std::setw(16) << d << std::setw(16) << sum1 << std::setw(16) << sum2 << std::endl; From cd0a27673e9ea088adb9072de8c3606050e2b3bc Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 18 Nov 2024 22:53:34 -0500 Subject: [PATCH 47/80] Fixes for FlatDisk with CUDA --- include/BiorthCyl.H | 3 ++- src/FlatDisk.H | 4 ++-- src/PolarBasis.H | 2 +- src/PolarBasis.cc | 2 +- src/cudaBiorthCyl.cu | 5 +++-- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/BiorthCyl.H b/include/BiorthCyl.H index d29d288a..962c1cb4 100644 --- a/include/BiorthCyl.H +++ b/include/BiorthCyl.H @@ -173,7 +173,8 @@ public: cacheInfo(const std::string& cachefile, bool verbose=true); #if HAVE_LIBCUDA==1 - void initialize_cuda(std::vector& cuArray, + void initialize_cuda(std::shared_ptr disk, + std::vector& cuArray, thrust::host_vector& tex); virtual cudaMappingConstants getCudaMappingConstants() diff --git a/src/FlatDisk.H b/src/FlatDisk.H index 3eecc2bc..e5eeb760 100644 --- a/src/FlatDisk.H +++ b/src/FlatDisk.H @@ -65,7 +65,7 @@ private: virtual void initialize_cuda() { sampT = floor(sqrt(component->CurTotal())); - ortho->initialize_cuda(cuInterpArray, tex); + ortho->initialize_cuda(disk, cuInterpArray, tex); } virtual cudaMappingConstants getCudaMappingConstants() @@ -83,7 +83,7 @@ private: //! Background evaluation virtual std::tuple - get_pot_background(double r, double z) + background(double r, double z) { return {disk->pot(r), -disk->dpot(r), 0.0}; } void get_dens(double r, double z, Eigen::MatrixXd& p, int tid); diff --git a/src/PolarBasis.H b/src/PolarBasis.H index caab18f0..bc835b4a 100644 --- a/src/PolarBasis.H +++ b/src/PolarBasis.H @@ -380,7 +380,7 @@ protected: //! Background evaluation virtual std::tuple - get_pot_background(double r, double z) { return {0.0, 0.0, 0.0}; } + background(double r, double z) { return {0.0, 0.0, 0.0}; } //! Thread method for accerlation compuation virtual void * determine_acceleration_and_potential_thread(void * arg); diff --git a/src/PolarBasis.cc b/src/PolarBasis.cc index a28edfe8..89beb1fe 100644 --- a/src/PolarBasis.cc +++ b/src/PolarBasis.cc @@ -1432,7 +1432,7 @@ void * PolarBasis::determine_acceleration_and_potential_thread(void * arg) if (not NO_M0) { if (M0_back) { - auto [p, drc, dzc] = get_pot_background(r, zz); + auto [p, drc, dzc] = background(r, zz); potl = mfac * p; potr = mfac * drc; diff --git a/src/cudaBiorthCyl.cu b/src/cudaBiorthCyl.cu index 218e7017..051824d7 100644 --- a/src/cudaBiorthCyl.cu +++ b/src/cudaBiorthCyl.cu @@ -84,7 +84,8 @@ thrust::host_vector returnTestBioCyl1 } void BiorthCyl::initialize_cuda -(std::vector& cuArray, +(std::shared_ptr disk, + std::vector& cuArray, thrust::host_vector& tex ) { @@ -192,7 +193,7 @@ void BiorthCyl::initialize_cuda for (int i=0; ibackground(r); tt[0][i] = p; tt[1][i] = dr; } From 56e5ac5c1b64a581d023a6e80288d40e73b15cb7 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 18 Nov 2024 22:57:09 -0500 Subject: [PATCH 48/80] Use EmpCyl2d::ModelCyl pot and dpot functions for background --- src/cudaBiorthCyl.cu | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/cudaBiorthCyl.cu b/src/cudaBiorthCyl.cu index 051824d7..e0106b0e 100644 --- a/src/cudaBiorthCyl.cu +++ b/src/cudaBiorthCyl.cu @@ -193,9 +193,8 @@ void BiorthCyl::initialize_cuda for (int i=0; ibackground(r); - tt[0][i] = p; - tt[1][i] = dr; + tt[0][i] = disk->pot(r); + tt[1][i] = -disk->dpot(r); } // Allocate CUDA array in device memory (a one-dimension 'channel') From 817892c9a62f924efeb455c44d20a4240eb92972 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Wed, 20 Nov 2024 16:59:37 -0500 Subject: [PATCH 49/80] Added CBDisk for testing; not optimized in any way and no CUDA implementation --- include/BiorthCyl.H | 2 + src/CBDisk.H | 92 ++++++++ src/CBDisk.cc | 510 ++++++++++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 6 +- src/Component.cc | 4 + src/cudaBiorthCyl.cu | 4 +- src/cudaPolarBasis.cu | 14 +- 7 files changed, 619 insertions(+), 13 deletions(-) create mode 100644 src/CBDisk.H create mode 100644 src/CBDisk.cc diff --git a/include/BiorthCyl.H b/include/BiorthCyl.H index 962c1cb4..5a4d2588 100644 --- a/include/BiorthCyl.H +++ b/include/BiorthCyl.H @@ -173,6 +173,8 @@ public: cacheInfo(const std::string& cachefile, bool verbose=true); #if HAVE_LIBCUDA==1 + //! Initialize CUDA arrays. 'disk' is the model object for the + //! background m=0 potential and radial force void initialize_cuda(std::shared_ptr disk, std::vector& cuArray, thrust::host_vector& tex); diff --git a/src/CBDisk.H b/src/CBDisk.H new file mode 100644 index 00000000..290c4b4a --- /dev/null +++ b/src/CBDisk.H @@ -0,0 +1,92 @@ +#ifndef _CBDisk_H +#define _CBDisk_H + +#include +#include + +#include + +//! Reference implemenation of the Clutton-Brock disk basis +class CBDisk : public PolarBasis +{ + +private: + + //@{ + //! 2D Clutton-Brock basis + double phif(const int n, const int m, const double r); + double dphi(const int n, const int m, const double r); + + double potl(const int n, const int m, const double r); + double dpot(const int n, const int m, const double r); + double dens(const int n, const int m, const double r); + + double norm(const int n, const int m); + + void potl(const int m, const double r, Eigen::VectorXd& a); + void dens(const int m, const double r, Eigen::VectorXd& a); + //@} + + //! Required maximum table radious (no limit here) + virtual double getRtable() { return std::numeric_limits::max(); } + + //! Sanity check + void orthoCheck(); + + void initialize(void); + + void get_dpotl(double r, double z, + Eigen::MatrixXd& p, Eigen::MatrixXd& dpr, Eigen::MatrixXd& dpz, int tid); + + void get_potl(double r, double z, Eigen::MatrixXd& p, int tid); + + //! Background instance + using Disk2d = std::shared_ptr; + Disk2d disk; + + //! Set background from YAML + void setBackground(); + + //! Background evaluation + virtual std::tuple + background(double r, double z) + { return {disk->pot(r), -disk->dpot(r), 0.0}; } + + void get_dens(double r, double z, Eigen::MatrixXd& p, int tid); + + void get_potl_dens(double r, double z, + Eigen::MatrixXd& p, Eigen::MatrixXd& d, int tid); + + // Parameters + double scale; + int mmax; + string model; + + //! Valid keys for YAML configurations + static const std::set valid_keys; + +protected: + + //! Clutton-Brock potential + void get_pot(Eigen::MatrixXd& Vc, Eigen::MatrixXd& Vs, double r, double z) + { + get_potl(r, z, Vc, 0); + Vs = Vc; + } + +public: + // Global parameters + /** Constructor + @param c0 is the instantiating caller (Component) + @param conf passes any parameters to basis instance + @param scale is the radius for coordinate scaling + */ + CBDisk(Component* c0, const YAML::Node& conf, MixtureBasis* m=0); + + //! Destructor + virtual ~CBDisk(); +}; + +#endif + + diff --git a/src/CBDisk.cc b/src/CBDisk.cc new file mode 100644 index 00000000..a968acde --- /dev/null +++ b/src/CBDisk.cc @@ -0,0 +1,510 @@ +#include + +#include + +#include "expand.H" + +#include +#include +#include + +const std::set +CBDisk::valid_keys = { + "mmax", + "scale", + "diskconf", + "background" +}; + +CBDisk::CBDisk(Component* c0, const YAML::Node& conf, MixtureBasis* m) : + PolarBasis(c0, conf, m) +{ + id = "CBDisk"; + + // Radial scale factor/softening radius + // + scale = 1.0; + + // Get initialization info + // + initialize(); + + if (myid==0) { + std::string sep("---- "); + std::cout << "---- CBDisk parameters: " + << std::endl << sep << "scale=" << scale + << std::endl << sep << "lmax=" << Lmax + << std::endl << sep << "nmax=" << nmax + << std::endl << sep << "NO_L0=" << std::boolalpha << NO_M0 + << std::endl << sep << "NO_L1=" << std::boolalpha << NO_M1 + << std::endl << sep << "EVEN_M=" << std::boolalpha << EVEN_M + << std::endl << sep << "M0_ONLY=" << std::boolalpha << M0_only + << std::endl << sep << "selfgrav=" << std::boolalpha << self_consistent + << std::endl; + } + + setup(); + if (myid==0) orthoCheck(); +} + + +void CBDisk::initialize() +{ + // Remove matched keys + // + for (auto v : valid_keys) current_keys.erase(v); + + // Assign values from YAML + // + try { + if (conf["mmax"]) mmax = conf["mmax"].as(); + if (conf["Lmax"]) mmax = conf["Lmax"].as(); + if (conf["Mmax"]) mmax = conf["Mmax"].as(); + if (conf["scale"]) scale = conf["scale"].as(); + + Lmax = Mmax = mmax; // Override base-class values + } + catch (YAML::Exception & error) { + if (myid==0) std::cout << "Error parsing parameters in CBDisk: " + << error.what() << std::endl + << std::string(60, '-') << std::endl + << "Config node" << std::endl + << std::string(60, '-') << std::endl + << conf << std::endl + << std::string(60, '-') << std::endl; + throw std::runtime_error("CBDisk::initialize: error in parsing YAML"); + } + + // Set background model + if (conf["background"]) setBackground(); +} + +CBDisk::~CBDisk(void) +{ + // NADA +} + + +void CBDisk::setBackground() +{ + try { + + YAML::Node Params = conf["background"]; + + std::string name = Params["name"].as(); + auto params = Params["parameters"]; + + // Convert ID string to lower case + // + std::transform(name.begin(), name.end(), name.begin(), + [](unsigned char c){ return std::tolower(c); }); + + if (name.find("kuzmin") != std::string::npos) { + if (myid==0) { + std::cout << "---- CBDisk: Making a Kuzmin disk"; + if (params) std::cout << " with" << std::endl << Params["parameters"]; + std::cout << std::endl; + } + disk = std::make_shared(params); + return; + } + + if (name.find("mestel") != std::string::npos) { + if (myid==0) { + std::cout << "---- CBDisk: Making a finite Mestel disk"; + if (params) std::cout << " with" << std::endl << Params["parameters"]; + std::cout << std::endl; + } + disk = std::make_shared(params); + return; + } + + if (name.find("zang") != std::string::npos) { + if (myid==0) { + std::cout << "---- CBDisk: Making a double-tapered Zang"; + YAML::Emitter out; + out << YAML::Flow << params; + if (params) std::cout << " with " << out.c_str(); + std::cout << std::endl; + } + disk = std::make_shared(params); + return; + } + + if (name.find("expon") != std::string::npos) { + if (myid==0) { + std::cout << "---- CBDisk: Making an Exponential disk"; + if (params) std::cout << " with" << std::endl << params; + std::cout << std::endl; + } + disk = std::make_shared(params); + return; + } + + // Default if nothing else matches + if (myid==0) + std::cout << "---- CBDisk: Making an Exponential disk [Default]" + << std::endl; + disk = std::make_shared(params); + return; + } + catch (YAML::Exception & error) { + if (myid==0) + std::cout << "Error parsing parameters in CBDisk::setBackground: " + << error.what() << std::endl + << std::string(60, '-') << std::endl + << "Config node" << std::endl + << std::string(60, '-') << std::endl + << conf["background"] << std::endl + << std::string(60, '-') << std::endl; + throw std::runtime_error("CBDisk::setBackground: error parsing YAML config"); + } +} + + +void CBDisk::get_dpotl(double r, double z, + Eigen::MatrixXd& pot, + Eigen::MatrixXd& dpr, + Eigen::MatrixXd& dpz, int tid) +{ + pot.resize(mmax+1, nmax); // Resize the return arrays + dpr.resize(mmax+1, nmax); + dpz.resize(mmax+1, nmax); + + dpz.setZero(); + + double R = r/scale; + + for (int m=0; m<=mmax; m++) { // Pack the array + for (int j=0; j0) ret -= 2.0*phif(n-1, m+1, r); + if (n>1) ret += phif(n-2, m+1, r); + return -r*ret; +} + + +// By recurrance relation +// +void CBDisk::potl(const int m, const double r, Eigen::VectorXd& a) +{ + a.resize(nmax); + + double pfac = pow(r, m); + + double r2 = r*r; + double fac = 1.0/(1.0 + r2); + double cur = sqrt(fac); + + for (int mm=1; mm<=m; mm++) cur *= fac*(2*mm - 1); + + a(0) = pfac*cur; + + if (nmax>0) { + + double curl1 = cur; + double curl2; + + fac *= r2 - 1.0; + cur *= fac*(2*m+1); + + a(1) = pfac*cur; + + for (int nn=2; nn=2) + return f*pow(r, m)*(phif(n, m+1, r) - phif(n-2, m+1, r)); + else + return f*pow(r, m)*phif(n, m+1, r); +} + + +void CBDisk::dens(int mm, double r, Eigen::VectorXd& a) +{ + a.resize(nmax); + + double pfac = pow(r, (double)mm+1.0e-20); + + int m = mm + 1; + double r2 = r*r; + double fac = 1.0/(1.0 + r2); + double cur = sqrt(fac); + + for (int M=1; M<=m; M++) cur *= fac*(2*M - 1); + + a(0) = pfac*cur; + + if (nmax>0) { + + double curl1 = cur; + double curl2; + + fac *= r2 - 1.0; + cur *= fac*(2*m+1); + + a(1) = pfac*cur; + + for (int nn=2; nn1; nn--) + a(nn) -= a(nn-2); + } + + for (int n=0; n worst(m)) worst(m) = fabs(test); + } + } + + if (worst(m) > tol) { + std::ostringstream sout; + sout << "CBDisk.ortho." << m << ".direct"; + std::ofstream out(sout.str()); + if (out) out << ortho << std::endl; + } + + } + + std::cout << std::endl + << hh << "--------------" << std::endl + << hh << "Direct test" << std::endl + << hh << "--------------" << std::endl + << hh << std::setw(6) << "m" << std::setw(16) << "worst" << std::endl + << hh << std::setw(6) << "-" << std::setw(16) << "-----" << std::endl; + + for (int m=0; m<=mmax; m++) + std::cout << hh << std::setw(6) << m << std::setw(16) << worst(m) << std::endl; + std::cout << std::endl; + + + // Recursion test + // + worst.setZero(); + + for (int m=0; m<=mmax; m++) { + + ortho.setZero(); + + for (int i=0; i worst(m)) worst(m) = fabs(test); + } + } + + if (worst(m) > tol) { + std::ostringstream sout; + sout << "CBDisk.ortho." << m << ".recurse"; + std::ofstream out(sout.str()); + if (out) out << ortho << std::endl; + } + + } + + std::cout << hh << "--------------" << std::endl + << hh << "Recursion test" << std::endl + << hh << "--------------" << std::endl + << hh << std::setw(6) << "m" << std::setw(16) << "worst" << std::endl + << hh << std::setw(6) << "-" << std::setw(16) << "-----" << std::endl; + for (int m=0; m<=mmax; m++) + std::cout << hh << std::setw(6) << m << std::setw(16) << worst(m) << std::endl; + std::cout << std::endl; + + const int numr = 10; + double dR = 3.0*scale/numr, dH = 0.01*scale; + + std::ofstream out("CBDisk.ortho.gradient"); + for (int m=0; m<=mmax; m++) { + for (int i=0; i #include #include +#include #include #include #include @@ -883,6 +884,9 @@ void Component::configure(void) else if ( !id.compare("flatdisk") ) { force = new FlatDisk(this, fconf); } + else if ( !id.compare("CBDisk") ) { + force = new CBDisk(this, fconf); + } else if ( !id.compare("direct") ) { force = new Direct(this, fconf); } diff --git a/src/cudaBiorthCyl.cu b/src/cudaBiorthCyl.cu index e0106b0e..468a7888 100644 --- a/src/cudaBiorthCyl.cu +++ b/src/cudaBiorthCyl.cu @@ -193,8 +193,8 @@ void BiorthCyl::initialize_cuda for (int i=0; ipot(r); - tt[1][i] = -disk->dpot(r); + tt[0][i] = disk->pot(r); + tt[1][i] = disk->dpot(r); } // Allocate CUDA array in device memory (a one-dimension 'channel') diff --git a/src/cudaPolarBasis.cu b/src/cudaPolarBasis.cu index 0e51e34c..ad6bb534 100644 --- a/src/cudaPolarBasis.cu +++ b/src/cudaPolarBasis.cu @@ -884,13 +884,11 @@ __global__ void coefKernelPlr3 const int tid = blockDim.x * blockIdx.x + threadIdx.x; const int N = lohi.second - lohi.first; - // The inner product of the potential-density pair is 1/(2*pi). So - // the biorthgonal norm is 2*pi*density. The inner product of the - // trig functions is 2*pi. So the norm is 1/sqrt(2*pi). The total - // norm is therefore 2*pi/sqrt(2*pi) = sqrt(2*pi) = 2.5066... + // 2d basis are assumed to be normed on input upto the sqrt(2) for + // the sine and cosine angular terms. // - cuFP_t norm = 2.5066282746310007; - if (m) norm = 3.5449077018110322; + cuFP_t norm = 1.0; + if (m) norm = 1.4142135623730951; for (int n=0; n P, dArray I, // Normalization constants // - cuFP_t norm0 = 0.39894228040143270286; - cuFP_t norm1 = 0.56418958354775627928; + cuFP_t norm0 = 1.0; + cuFP_t norm1 = 1.4142135623730951; cuFP_t norm; int muse = mmax > mlim ? mlim : mmax; From 3ebd4536b8be45dd19257079d987dd9c84f0fe63 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 21 Nov 2024 13:53:55 -0500 Subject: [PATCH 50/80] Fix seg fault when exiting before output or container object is instantiated --- src/OutputContainer.H | 2 +- src/end.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OutputContainer.H b/src/OutputContainer.H index 9d512b32..520611a7 100644 --- a/src/OutputContainer.H +++ b/src/OutputContainer.H @@ -32,7 +32,7 @@ public: void initialize(); //! Execute the all methods in the container - void Run(int nstep, int mstep=std::numeric_limits::max(), bool last=false); + void Run(int nstep, int mstep=std::numeric_limits::max(), bool final=false); }; #endif diff --git a/src/end.cc b/src/end.cc index 2f7c0afb..008a03e7 100644 --- a/src/end.cc +++ b/src/end.cc @@ -15,9 +15,9 @@ void clean_up(void) { // Call for final output to files - output->Run(this_step, 0, true); + if (output) output->Run(this_step, 0, true); // Cache for restart - external->finish(); + if (external) external->finish(); MPI_Barrier(MPI_COMM_WORLD); From 18aa52bca85b1d390489c2fc56cde262987f3e7d Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 21 Nov 2024 16:08:39 -0500 Subject: [PATCH 51/80] Zero background arrays and only fill if disk object is instantiated --- src/cudaBiorthCyl.cu | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/cudaBiorthCyl.cu b/src/cudaBiorthCyl.cu index 468a7888..5a579020 100644 --- a/src/cudaBiorthCyl.cu +++ b/src/cudaBiorthCyl.cu @@ -187,14 +187,19 @@ void BiorthCyl::initialize_cuda // Add background arrays // std::vector> tt(ndim2); - for (auto & v : tt) v.resize(numr); + for (auto & v : tt) { + v.resize(numr); + thrust::fill(v.begin(), v.end(), 0.0); + } - double dx0 = (xmax - xmin)/(numr - 1); + if (disk) { + double dx0 = (xmax - xmin)/(numr - 1); - for (int i=0; ipot(r); - tt[1][i] = disk->dpot(r); + for (int i=0; ipot(r); + tt[1][i] = disk->dpot(r); + } } // Allocate CUDA array in device memory (a one-dimension 'channel') From b2a15b9d1494cb66236d578b6c2fe7f445458faa Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 21 Nov 2024 17:08:10 -0500 Subject: [PATCH 52/80] Missing flag disk tag --- src/CBDisk.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CBDisk.cc b/src/CBDisk.cc index a968acde..2f1aba94 100644 --- a/src/CBDisk.cc +++ b/src/CBDisk.cc @@ -20,6 +20,7 @@ CBDisk::CBDisk(Component* c0, const YAML::Node& conf, MixtureBasis* m) : PolarBasis(c0, conf, m) { id = "CBDisk"; + is_flat = true; // Radial scale factor/softening radius // From 6e5dbe4125343ee7644733eb29978a20cb8c289b Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 21 Nov 2024 17:08:36 -0500 Subject: [PATCH 53/80] Allow 2d and 3d norm switch; only 2d currently used so this makes no change --- src/PolarBasis.cc | 48 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/src/PolarBasis.cc b/src/PolarBasis.cc index 89beb1fe..aae196ae 100644 --- a/src/PolarBasis.cc +++ b/src/PolarBasis.cc @@ -401,8 +401,20 @@ void * PolarBasis::determine_coefficients_thread(void * arg) { // For biorthogonal density component and normalization // - constexpr double norm0 = 1.0; - constexpr double norm1 = M_SQRT2; + constexpr double norm0_3d = 2.0*M_PI * 0.5*M_2_SQRTPI/M_SQRT2; + constexpr double norm1_3d = 2.0*M_PI * 0.5*M_2_SQRTPI; + + constexpr double norm0_2d = 1.0; + constexpr double norm1_2d = M_SQRT2; + + double norm0, norm1; + if (is_flat) { + norm0 = norm0_2d; + norm1 = norm1_2d; + } else { + norm0 = norm0_3d; + norm1 = norm1_3d; + } double r, r2, facL=1.0, fac1, fac2, phi, mass; double xx, yy, zz; @@ -1044,8 +1056,20 @@ void PolarBasis::multistep_update(int from, int to, Component *c, int i, int id) // For biorthogonal density component and normalization // - constexpr double norm0 = 1.0; - constexpr double norm1 = M_SQRT2; + constexpr double norm0_3d = 2.0*M_PI * 0.5*M_2_SQRTPI/M_SQRT2; + constexpr double norm1_3d = 2.0*M_PI * 0.5*M_2_SQRTPI; + + constexpr double norm0_2d = 1.0; + constexpr double norm1_2d = M_SQRT2; + + double norm0, norm1; + if (is_flat) { + norm0 = norm0_2d; + norm1 = norm1_2d; + } else { + norm0 = norm0_3d; + norm1 = norm1_3d; + } double mass = c->Mass(i) * component->Adiabatic(); @@ -1323,8 +1347,20 @@ void PolarBasis::compute_multistep_coefficients() void * PolarBasis::determine_acceleration_and_potential_thread(void * arg) { - constexpr double norm0 = 1.0; - constexpr double norm1 = M_SQRT2; + constexpr double norm0_3d = 0.5*M_2_SQRTPI/M_SQRT2; + constexpr double norm1_3d = 0.5*M_2_SQRTPI; + + constexpr double norm0_2d = 1.0; + constexpr double norm1_2d = M_SQRT2; + + double norm0, norm1; + if (is_flat) { + norm0 = norm0_2d; + norm1 = norm1_2d; + } else { + norm0 = norm0_3d; + norm1 = norm1_3d; + } double r, r0=0.0, phi; double potr, potz, potl, potp, p, pc, drc, drs, dzc, dzs, ps, dfacp, facdp; From fac56092ef3f614524c731cb259624d73510523a Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 21 Nov 2024 17:47:48 -0500 Subject: [PATCH 54/80] Output updates only --- expui/BasisFactory.cc | 3 + expui/BiorthBasis.H | 158 +++++++++- expui/BiorthBasis.cc | 662 ++++++++++++++++++++++++++++++++++++++++- pyEXP/BasisWrappers.cc | 179 ++++++++++- src/CBDisk.cc | 6 +- 5 files changed, 986 insertions(+), 22 deletions(-) diff --git a/expui/BasisFactory.cc b/expui/BasisFactory.cc index 5b574a8e..d3c11304 100644 --- a/expui/BasisFactory.cc +++ b/expui/BasisFactory.cc @@ -184,6 +184,9 @@ namespace BasisClasses else if ( !name.compare("flatdisk") ) { basis = std::make_shared(conf); } + else if ( !name.compare("CBDisk") ) { + basis = std::make_shared(conf); + } else if ( !name.compare("slabSL") ) { basis = std::make_shared(conf); } diff --git a/expui/BiorthBasis.H b/expui/BiorthBasis.H index a4af27c1..ab685859 100644 --- a/expui/BiorthBasis.H +++ b/expui/BiorthBasis.H @@ -529,7 +529,7 @@ namespace BasisClasses //! Compute the orthogonality of the basis by returning inner //! produce matrices - std::vector orthoCheck(); + std::vector orthoCheck(int knots=400); //! Biorthogonality sanity check bool orthoTest() @@ -542,6 +542,154 @@ namespace BasisClasses }; + /** + Uses the Clutton-Brock basis to evaluate expansion coeffients and + provide potential and density basis fields + */ + class CBDisk : public BiorthBasis + { + + public: + + using BasisMap = std::map; + using BasisArray = std::vector>; + + private: + + //! Helper for constructor + void initialize(); + + int mmax, nmax; + double scale; + + bool NO_M0, NO_M1, EVEN_M, M0_only; + + std::vector potd, potR, potZ, dend; + + Eigen::MatrixXd expcoef; + int N1, N2; + int used; + + using matT = std::vector; + using vecT = std::vector; + + double totalMass; + int npart; + + Eigen::VectorXd work; + + //! For coefficient writing + typedef Eigen::Matrix + EigenColMajor; + + //! Get potential + void get_pot(Eigen::MatrixXd& tab, double r); + + //! Get density + void get_dens(Eigen::MatrixXd& tab, double r); + + //! Get force + void get_force(Eigen::MatrixXd& tab, double r); + + //@{ + //! 2D Clutton-Brock basis + double phif(const int n, const int m, const double r); + double dphi(const int n, const int m, const double r); + + double potl(const int n, const int m, const double r); + double dpot(const int n, const int m, const double r); + double dens(const int n, const int m, const double r); + + double norm(const int n, const int m); + + void potl(const int m, const double r, Eigen::VectorXd& a); + void dpot(const int m, const double r, Eigen::VectorXd& a); + void dens(const int m, const double r, Eigen::VectorXd& a); + //@} + + protected: + + //! Evaluate basis in cylindrical coordinates + virtual std::vector + cyl_eval(double R, double z, double phi); + + //! Evaluate basis in spherical coordinates. Conversion from the + //! cylindrical evaluation above. + virtual std::vector + sph_eval(double r, double costh, double phi); + + // Cartesian + virtual std::vector + crt_eval(double x, double y, double z); + + //! Load coefficients into the new CoefStruct + virtual void load_coefs(CoefClasses::CoefStrPtr coefs, double time); + + //! Set coefficients + virtual void set_coefs(CoefClasses::CoefStrPtr coefs); + + //! Valid keys for YAML configurations + static const std::set valid_keys; + + //! Return readable class name + virtual const std::string classname() { return "CBDisk";} + + //! Subspace index + virtual const std::string harmonic() { return "m";} + + public: + + //! Constructor from YAML node + CBDisk(const YAML::Node& conf); + + //! Constructor from YAML string + CBDisk(const std::string& confstr); + + //! Destructor + virtual ~CBDisk(void) {} + + //! Print and return the cache parameters + static std::map + cacheInfo(const std::string& cachefile) + { + return BiorthCyl::cacheInfo(cachefile); + } + + //! Zero out coefficients to prepare for a new expansion + void reset_coefs(void); + + //! Make coefficients after accumulation + void make_coefs(void); + + //! Accumulate new coefficients + virtual void accumulate(double x, double y, double z, double mass); + + //! Return current maximum harmonic order in expansion + int getMmax() { return mmax; } + + //! Return current maximum order in radial expansion + int getNmax() { return nmax; } + + //! Return a vector of a vector of 1d basis-function grids for + //! CBDisk, logarithmically spaced between [logxmin, logxmax] + //! (base 10). + BasisArray getBasis(double logxmin=-3.0, double logxmax=0.5, int numgrid=2000); + + //! Compute the orthogonality of the basis by returning inner + //! produce matrices + std::vector orthoCheck(int knots=4000); + + //! Biorthogonality sanity check + bool orthoTest() + { + auto [ret, worst, lworst] = orthoCompute(orthoCheck()); + // For the CTest log + std::cout << "CBDisk::orthoTest: worst=" << worst << std::endl; + return ret; + } + + }; + /** Uses EmpCylSL basis to evaluate expansion coeffients and provide potential and density basis fields @@ -676,7 +824,7 @@ namespace BasisClasses //! Compute the orthogonality of the basis by returning inner //! produce matrices - std::vector orthoCheck() + std::vector orthoCheck(int knots=40) { return sl->orthoCheck(); } @@ -813,7 +961,7 @@ namespace BasisClasses //! Compute the orthogonality of the basis by returning inner //! produce matrices - std::vector orthoCheck(); + std::vector orthoCheck(int knots=40); //! Biorthogonality sanity check bool orthoTest() @@ -937,12 +1085,12 @@ namespace BasisClasses //! Compute the orthogonality of the basis by returning inner //! produce matrices - Eigen::MatrixXcd orthoCheck(); + std::vector orthoCheck(int knots=40); //! Biorthogonality sanity check bool orthoTest() { - auto c = orthoCheck(); + auto c = orthoCheck()[0]; double worst = 0.0; for (int n1=0; n1 FlatDisk::orthoCheck() + std::vector FlatDisk::orthoCheck(int knots) { return ortho->orthoCheck(); } @@ -1996,6 +1997,657 @@ namespace BasisClasses return ret; } + const std::set + CBDisk::valid_keys = { + "self_consistent", + "NO_M0", + "NO_M1", + "EVEN_M", + "M0_BACK", + "M0_ONLY", + "NO_MONO", + "background", + "playback", + "coefMaster", + "scale", + "Lmax", + "Mmax", + "nmax", + "mmax", + "mlim", + "dof", + "subsamp", + "samplesz", + "vtkfreq", + "tksmooth", + "tkcum", + "tk_type" + }; + + CBDisk::CBDisk(const YAML::Node& CONF) : + BiorthBasis(CONF, "CBDisk") + { + initialize(); + } + + CBDisk::CBDisk(const std::string& confstr) : + BiorthBasis(confstr, "CBDisk") + { + initialize(); + } + + void CBDisk::initialize() + { + + // Assign some defaults + // + mmax = 6; + nmax = 18; + scale = 1.0; + + // Check for unmatched keys + // + auto unmatched = YamlCheck(conf, valid_keys); + if (unmatched.size()) + throw YamlConfigError("Basis::Basis::CBDisk", "parameter", unmatched, __FILE__, __LINE__); + + // Assign values from YAML + // + try { + if (conf["Lmax"]) mmax = conf["Lmax"].as(); // Proxy + if (conf["Mmax"]) mmax = conf["Mmax"].as(); // Proxy + if (conf["mmax"]) mmax = conf["mmax"].as(); + if (conf["nmax"]) nmax = conf["nmax"].as(); + if (conf["scale"]) scale = conf["scale"].as(); + + N1 = 0; + N2 = std::numeric_limits::max(); + NO_M0 = NO_M1 = EVEN_M = M0_only = false; + + if (conf["N1"] ) N1 = conf["N1"].as(); + if (conf["N2"] ) N2 = conf["N2"].as(); + if (conf["NO_M0"]) NO_M0 = conf["NO_M0"].as(); + if (conf["NO_M1"]) NO_M1 = conf["NO_M1"].as(); + if (conf["EVEN_M"]) EVEN_M = conf["EVEN_M"].as(); + if (conf["M0_ONLY"]) M0_only = conf["M0_ONLY"].as(); + } + catch (YAML::Exception & error) { + if (myid==0) std::cout << "Error parsing parameter stanza for <" + << name << ">: " + << error.what() << std::endl + << std::string(60, '-') << std::endl + << conf << std::endl + << std::string(60, '-') << std::endl; + + throw std::runtime_error("CBDisk: error parsing YAML"); + } + + // Set characteristic radius defaults + // + if (not conf["scale"]) conf["scale"] = 1.0; + + // Orthogonality sanity check + // + orthoTest(); + + // Get max threads + // + int nthrds = omp_get_max_threads(); + + // Allocate memory + // + potd.resize(nthrds); + potR.resize(nthrds); + dend.resize(nthrds); + + for (auto & v : potd) v.resize(mmax+1, nmax); + for (auto & v : potR) v.resize(mmax+1, nmax); + for (auto & v : dend) v.resize(mmax+1, nmax); + + expcoef.resize(2*mmax+1, nmax); + expcoef.setZero(); + + work.resize(nmax); + + used = 0; + + // Set cylindrical coordindates + // + coordinates = Coord::Cylindrical; + } + + // Get potential + void CBDisk::get_pot(Eigen::MatrixXd& tab, double r) + { + tab.resize(mmax+1, nmax); + Eigen::VectorXd a(nmax); + for (int m=0; m<=mmax; m++) { + potl(m, r, a); + tab.row(m) = a; + } + } + + // Get density + void CBDisk::get_dens(Eigen::MatrixXd& tab, double r) + { + tab.resize(mmax+1, nmax); + Eigen::VectorXd a(nmax); + for (int m=0; m<=mmax; m++) { + dens(m, r, a); + tab.row(m) = a; + } + } + + // Get force + void CBDisk::get_force(Eigen::MatrixXd& tab, double r) + { + tab.resize(mmax+1, nmax); + Eigen::VectorXd a(nmax); + for (int m=0; m<=mmax; m++) { + dpot(m, r, a); + tab.row(m) = a; + } + } + + // Routines for computing biorthonormal pairs based on + // Clutton-Brock's 2-dimensional series + // + double CBDisk::phif(const int n, const int m, const double r) + { + // By recurrance relation + // + double r2 = r*r; + double fac = 1.0/(1.0 + r2); + double cur = sqrt(fac); + + for (int mm=1; mm<=m; mm++) cur *= fac*(2*mm - 1); + + if (n==0) return cur; + + double curl1 = cur; + double curl2; + + fac *= r2 - 1.0; + cur *= fac*(2*m+1); + + if (n==1) return cur; + + for (int nn=2; nn<=n; nn++) { + curl2 = curl1; + curl1 = cur; + cur = (2.0 + (double)(2*m-1)/nn)*fac*curl1 - + (1.0 + (double)(2*m-1)/nn)*curl2; + } + + return cur; + } + + double CBDisk::potl(const int n, const int m, const double r) + { + return pow(r, m) * phif(n, m, r)/sqrt(norm(n, m)); + } + + double CBDisk::dpot(const int n, const int m, const double r) + { + double ret = dphi(n, m, r); + if (m) ret = (phif(n, m, r)*m/r + ret) * pow(r, m); + return ret/sqrt(norm(n, m)); + } + + double CBDisk::dphi(const int n, const int m, const double r) + { + double ret = phif(n, m+1, r); + if (n>0) ret -= 2.0*phif(n-1, m+1, r); + if (n>1) ret += phif(n-2, m+1, r); + return -r*ret; + } + + + // By recurrance relation + // + void CBDisk::potl(const int m, const double r, Eigen::VectorXd& a) + { + a.resize(nmax); + + double pfac = pow(r, m); + + double r2 = r*r; + double fac = 1.0/(1.0 + r2); + double cur = sqrt(fac); + + for (int mm=1; mm<=m; mm++) cur *= fac*(2*mm - 1); + + a(0) = pfac*cur; + + if (nmax>0) { + + double curl1 = cur; + double curl2; + + fac *= r2 - 1.0; + cur *= fac*(2*m+1); + + a(1) = pfac*cur; + + for (int nn=2; nn(m)/r; + + Eigen::VectorXd b(nmax); + potl(m+1, r, b); + + for (int n=0; n0) a(n) += 2.0*b(n-1); + if (n>1) a(n) -= b(n-2); + } + } + + double CBDisk::dens(int n, int m, double r) + { + double f = 0.5/sqrt(norm(n, m))/M_PI; + + if (n>=2) + return f*pow(r, m)*(phif(n, m+1, r) - phif(n-2, m+1, r)); + else + return f*pow(r, m)*phif(n, m+1, r); + } + + + void CBDisk::dens(int mm, double r, Eigen::VectorXd& a) + { + a.resize(nmax); + + double pfac = pow(r, (double)mm+1.0e-20); + + int m = mm + 1; + double r2 = r*r; + double fac = 1.0/(1.0 + r2); + double cur = sqrt(fac); + + for (int M=1; M<=m; M++) cur *= fac*(2*M - 1); + + a(0) = pfac*cur; + + if (nmax>0) { + + double curl1 = cur; + double curl2; + + fac *= r2 - 1.0; + cur *= fac*(2*m+1); + + a(1) = pfac*cur; + + for (int nn=2; nn1; nn--) + a(nn) -= a(nn-2); + } + + for (int n=0; n CBDisk::orthoCheck(int num) + { + const double tol = 1.0e-4; + LegeQuad lq(num); + + std::vector ret(mmax+1); + for (auto & v : ret) v.resize(nmax, nmax); + + double Rmax = scale*100.0; + + for (int m=0; m<=mmax; m++) { + + ret[m].setZero(); + + for (int i=0; i0 && expcoef.cols()>0) expcoef.setZero(); + totalMass = 0.0; + used = 0; + } + + + void CBDisk::load_coefs(CoefClasses::CoefStrPtr coef, double time) + { + CoefClasses::CylStruct* cf = dynamic_cast(coef.get()); + + cf->mmax = mmax; + cf->nmax = nmax; + cf->time = time; + + cf->store((2*mmax+1)*nmax); + cf->coefs = std::make_shared + (cf->store.data(), 2*mmax+1, nmax); + + for (int m=0, m0=0; m<=mmax; m++) { + for (int n=0; ncoefs)(m, n) = {expcoef(m0, n), 0.0}; + else + (*cf->coefs)(m, n) = {expcoef(m0, n), expcoef(m0+1, n)}; + } + if (m==0) m0 += 1; + else m0 += 2; + } + } + + void CBDisk::set_coefs(CoefClasses::CoefStrPtr coef) + { + // Sanity check on derived class type + // + if (not dynamic_cast(coef.get())) + throw std::runtime_error("CBDisk::set_coefs: you must pass a CoefClasses::CylStruct"); + + // Sanity check on dimensionality + // + { + auto cc = dynamic_cast(coef.get()); + auto cf = cc->coefs; + int rows = cf->rows(); + int cols = cf->cols(); + if (rows != mmax+1 or cols != nmax) { + std::ostringstream sout; + sout << "CBDisk::set_coefs: the basis has (mmax+1, nmax)=(" + << mmax+1 << ", " << nmax + << "). The coef structure has (rows, cols)=(" + << rows << ", " << cols << ")"; + + throw std::runtime_error(sout.str()); + } + } + + CoefClasses::CylStruct* cf = dynamic_cast(coef.get()); + auto & cc = *cf->coefs; + + // Assign internal coefficient table (doubles) from the complex struct + // + for (int m=0, m0=0; m<=mmax; m++) { + for (int n=0; nctr.size()) + coefctr = cf->ctr; + else + coefctr = {0.0, 0.0, 0.0}; + } + + void CBDisk::accumulate(double x, double y, double z, double mass) + { + // Normalization factors + // + constexpr double norm0 = 2.0*M_PI * 0.5*M_2_SQRTPI/M_SQRT2; + constexpr double norm1 = 2.0*M_PI * 0.5*M_2_SQRTPI; + + //====================== + // Compute coefficients + //====================== + + double R2 = x*x + y*y; + double R = sqrt(R); + + // Get thread id + int tid = omp_get_thread_num(); + + used++; + totalMass += mass; + + double phi = atan2(y, x); + + get_pot(potd[tid], R); + + // M loop + for (int m=0, moffset=0; m<=mmax; m++) { + + if (m==0) { + for (int n=0; nCBDisk::cyl_eval(double R, double z, double phi) + { + // Get thread id + int tid = omp_get_thread_num(); + + // Fixed values + constexpr double norm0 = 1.0; + constexpr double norm1 = M_SQRT2; + + double den0=0, den1=0, pot0=0, pot1=0, rpot=0, zpot=0, ppot=0; + + // Get the basis fields + // + get_dens (dend[tid], R); + get_pot (potd[tid], R); + get_force (potR[tid], R); + + // m loop + // + for (int m=0, moffset=0; m<=mmax; m++) { + + if (m==0 and NO_M0) { moffset++; continue; } + if (m==1 and NO_M1) { moffset += 2; continue; } + if (EVEN_M and m/2*2 != m) { moffset += 2; continue; } + if (m>0 and M0_only) break; + + if (m==0) { + for (int n=std::max(0, N1); n<=std::min(nmax-1, N2); n++) { + den0 += expcoef(0, n) * dend[tid](0, n) * norm0; + pot0 += expcoef(0, n) * potd[tid](0, n) * norm0; + rpot += expcoef(0, n) * potR[tid](0, n) * norm0; + } + + moffset++; + } else { + double cosm = cos(phi*m), sinm = sin(phi*m); + double vc, vs; + + vc = vs = 0.0; + for (int n=std::max(0, N1); n<=std::min(nmax-1, N2); n++) { + vc += expcoef(moffset+0, n) * dend[tid](m, n); + vs += expcoef(moffset+1, n) * dend[tid](m, n); + } + + den1 += (vc*cosm + vs*sinm) * norm1; + + vc = vs = 0.0; + for (int n=std::max(0, N1); n<=std::min(nmax-1, N2); n++) { + vc += expcoef(moffset+0, n) * potd[tid](m, n); + vs += expcoef(moffset+1, n) * potd[tid](m, n); + } + + pot1 += ( vc*cosm + vs*sinm) * norm1; + ppot += (-vc*sinm + vs*cosm) * m * norm1; + + vc = vs = 0.0; + for (int n=std::max(0, N1); n<=std::min(nmax-1, N2); n++) { + vc += expcoef(moffset+0, n) * potR[tid](m, n); + vs += expcoef(moffset+1, n) * potR[tid](m, n); + } + + rpot += (vc*cosm + vs*sinm) * norm1; + + moffset +=2; + } + } + + den0 *= -1.0; + den1 *= -1.0; + pot0 *= -1.0; + pot1 *= -1.0; + rpot *= -1.0; + ppot *= -1.0; + + return {den0, den1, den0+den1, pot0, pot1, pot0+pot1, rpot, zpot, ppot}; + } + + + std::vector CBDisk::sph_eval(double r, double costh, double phi) + { + // Cylindrical coords + // + double sinth = sqrt(fabs(1.0 - costh*costh)); + double R = r*sinth, z = r*costh; + + auto v = cyl_eval(R, z, phi); + + // Spherical force element converstion + // + double potr = v[6]*sinth + v[7]*costh; + double pott = v[6]*costh - v[7]*sinth; + + return {v[0], v[1], v[2], v[3], v[4], v[5], potr, pott, v[8]}; + } + + std::vector CBDisk::crt_eval(double x, double y, double z) + { + // Cylindrical coords from Cartesian + // + double R = sqrt(x*x + y*y) + 1.0e-18; + double phi = atan2(y, x); + + auto v = cyl_eval(R, z, phi); + + double potx = v[4]*x/R - v[8]*y/R; + double poty = v[4]*y/R + v[8]*x/R; + + return {v[0], v[1], v[2], v[3], v[4], v[5], potx, poty, v[7]}; + } + + CBDisk::BasisArray CBDisk::getBasis + (double logxmin, double logxmax, int numgrid) + { + // Assing return storage + BasisArray ret(mmax+1); + for (auto & v : ret) { + v.resize(nmax); + for (auto & u : v) { + u["potential"].resize(numgrid); // Potential + u["density" ].resize(numgrid); // Density + u["rforce" ].resize(numgrid); // Radial force + } + } + + // Radial grid spacing + double dx = (logxmax - logxmin)/(numgrid-1); + + // Basis storage + Eigen::MatrixXd tabpot, tabden, tabrfc; + + // Evaluate on the plane + for (int i=0; i Slab::valid_keys = { "nmaxx", @@ -2442,7 +3094,7 @@ namespace BasisClasses return ret; } - std::vector Slab::orthoCheck() + std::vector Slab::orthoCheck(int knots) { return ortho->orthoCheck(); } @@ -2747,9 +3399,11 @@ namespace BasisClasses return {0, den1, den1, 0, pot1, pot1, potr, pott, potp}; } - Eigen::MatrixXcd Cube::orthoCheck() + std::vector Cube::orthoCheck(int knots) { - return ortho->orthoCheck(); + std::vector ret; + ret.push_back(ortho->orthoCheck().array().abs()); + return ret; } // Generate coeffients from a particle reader diff --git a/pyEXP/BasisWrappers.cc b/pyEXP/BasisWrappers.cc index e0b72ab2..8393b131 100644 --- a/pyEXP/BasisWrappers.cc +++ b/pyEXP/BasisWrappers.cc @@ -33,19 +33,21 @@ void BasisFactoryClasses(py::module &m) 3. Cylindrical, created created by computing empirical orthogonal functions over a densely sampled SphericalSL basis; - 4. FlatDisk, an EOF rotation of the finite Bessel basis; and + 4. FlatDisk, an EOF rotation of the finite Bessel basis; - 5. Slab, a biorthogonal basis for a slab geometry with a finite + 5. CBDisk, the Clutton-Brock disk basis for testing; + + 6. Slab, a biorthogonal basis for a slab geometry with a finite finite vertical extent. The basis is constructed from direct solution of the Sturm-Liouville equation. - 6. Cube, a periodic cube basis whose functions are the Cartesian + 7. Cube, a periodic cube basis whose functions are the Cartesian eigenfunctions of the Cartesian Laplacian: sines and cosines. - 7. FieldBasis, for computing user-provided quantities from a + 8. FieldBasis, for computing user-provided quantities from a phase-space snapshot. - 8. VelocityBasis, for computing the mean field velocity fields from + 9. VelocityBasis, for computing the mean field velocity fields from a phase-space snapshot. This is a specialized version of FieldBasis. Each of these bases take a YAML configuration file as input. These parameter @@ -159,11 +161,13 @@ void BasisFactoryClasses(py::module &m) 3. FlatDisk uses cylindrical coordinates - 4. Slab uses Cartesian coordinates + 4. CBDisk uses cylindrical coordinates + + 5. Slab uses Cartesian coordinates - 5. Cube uses Cartesian coordinates + 6. Cube uses Cartesian coordinates - 6. FieldBasis and VelocityBasis provides two natural geometries for + 7. FieldBasis and VelocityBasis provides two natural geometries for field evaluation: a two-dimensional (dof=2) polar disk and a three-dimensional (dof=3) spherical geometry that are chosen using the 'dof' parameter. These use cylindrical and spherical @@ -582,6 +586,71 @@ void BasisFactoryClasses(py::module &m) }; + class PyCBDisk : public CBDisk + { + protected: + + std::vector sph_eval(double r, double costh, double phi) override + { + PYBIND11_OVERRIDE(std::vector, CBDisk, sph_eval, r, costh, phi); + } + + std::vector cyl_eval(double R, double z, double phi) override + { + PYBIND11_OVERRIDE(std::vector, CBDisk, cyl_eval, R, z, phi); + } + + std::vector crt_eval(double x, double y, double z) override + { + PYBIND11_OVERRIDE(std::vector, CBDisk, crt_eval, x, y, z); + } + + void load_coefs(CoefClasses::CoefStrPtr coefs, double time) override + { + PYBIND11_OVERRIDE(void, CBDisk, load_coefs, coefs, time); + } + + void set_coefs(CoefClasses::CoefStrPtr coefs) override + { + PYBIND11_OVERRIDE(void, CBDisk, set_coefs, coefs); + } + + const std::string classname() override { + PYBIND11_OVERRIDE(std::string, CBDisk, classname); + } + + const std::string harmonic() override { + PYBIND11_OVERRIDE(std::string, CBDisk, harmonic); + } + + public: + + // Inherit the constructors + using CBDisk::CBDisk; + + std::vector getFields(double x, double y, double z) override + { + PYBIND11_OVERRIDE(std::vector, CBDisk, getFields, x, y, z); + } + + void accumulate(double x, double y, double z, double mass) override + { + PYBIND11_OVERRIDE(void, CBDisk, accumulate, x, y, z, mass); + } + + void reset_coefs(void) override + { + PYBIND11_OVERRIDE(void, CBDisk, reset_coefs,); + } + + void make_coefs(void) override + { + PYBIND11_OVERRIDE(void, CBDisk, make_coefs,); + } + + }; + + class PySlab : public Slab { protected: @@ -1064,9 +1133,9 @@ void BasisFactoryClasses(py::module &m) Set the coordinate system for force evaluations. The natural coordinates for the basis class are the default; spherical coordinates for SphericalSL and Bessel, cylindrical coordinates for - Cylindrical and FlatDisk, and Cartesian coordinates for the Slab - and Cube. This member function can be used to override the default. - The available coorindates are: 'spherical', 'cylindrical', + Cylindrical, FlatDisk, and CBDisk, and Cartesian coordinates for the + Slab and Cube. This member function can be used to override the + default. The available coorindates are: 'spherical', 'cylindrical', 'cartesian'. Parameters @@ -1578,6 +1647,94 @@ void BasisFactoryClasses(py::module &m) )", py::arg("cachefile")); + py::class_, PyCBDisk, BasisClasses::BiorthBasis>(m, "CBDisk") + .def(py::init(), + R"( + Create a Clutton-Brock 2d disk basis + + Parameters + ---------- + YAMLstring : str + The YAML configuration for the razor-thin EOF basis. The default + parameters will give an exponential disk with scale length of + 0.01 units. Set the disk scale length using the 'scale' parameter. + + Returns + ------- + CBDisk + the new instance + )", py::arg("YAMLstring")) + .def("getBasis", &BasisClasses::CBDisk::getBasis, + R"( + Evaluate the potential-density basis functions + + Returned functions will linearly spaced 2d-grid for inspection. The + min/max radii are given in log_10 units. The structure is a two-grid of + dimension mmax by nmax each pointing to a dictionary of 1-d arrays + ('potential', 'density', 'rforce') of dimension numr. + + Parameters + ---------- + logxmin : float, default=-4.0 + the minimum radius in log10 scaled units + logxmax : float, default=-1.0 + the maximum radius in log10 scaled units + numr : int + the number of output evaluations + + Returns + ------- + list(dict{str: numpy.ndarray}) + list of lists of dictionaries in harmonic and radial pointing to + density and potential basis functions + )", + py::arg("logxmin")=-4.0, + py::arg("logxmax")=-1.0, + py::arg("numr")=400) + // The following member needs to be a lambda capture because + // orthoCheck is not in the base class and needs to have different + // parameters depending on the basis type. Here, the quadrature + // is determined by the scale of the meridional grid. + .def("orthoCheck", [](BasisClasses::CBDisk& A) + { + return A.orthoCheck(); + }, + R"( + Check orthgonality of basis functions by quadrature + + Inner-product matrix of Sturm-Liouville solutions indexed by + harmonic order used to assess fidelity. + + Parameters + ---------- + knots : int, default=40 + Number of quadrature knots + + Returns + ------- + list(numpy.ndarray) + list of numpy.ndarrays from [0, ... , Mmax] + )" + ) + .def_static("cacheInfo", [](std::string cachefile) + { + return BasisClasses::CBDisk::cacheInfo(cachefile); + }, + R"( + Report the parameters in a basis cache file and return a dictionary + + Parameters + ---------- + cachefile : str + name of cache file + + Returns + ------- + out : dict({tag: value}) + cache parameters + )", + py::arg("cachefile")); + py::class_, PySlab, BasisClasses::BiorthBasis>(m, "Slab") .def(py::init(), R"( diff --git a/src/CBDisk.cc b/src/CBDisk.cc index a968acde..ee8d9836 100644 --- a/src/CBDisk.cc +++ b/src/CBDisk.cc @@ -430,7 +430,9 @@ void CBDisk::orthoCheck() } - std::cout << std::endl + std::cout << hh << std::endl + << hh << "Orthogonality check" << std::endl + << hh << std::endl << hh << "--------------" << std::endl << hh << "Direct test" << std::endl << hh << "--------------" << std::endl @@ -488,7 +490,7 @@ void CBDisk::orthoCheck() << hh << std::setw(6) << "-" << std::setw(16) << "-----" << std::endl; for (int m=0; m<=mmax; m++) std::cout << hh << std::setw(6) << m << std::setw(16) << worst(m) << std::endl; - std::cout << std::endl; + std::cout << hh << std::endl; const int numr = 10; double dR = 3.0*scale/numr, dH = 0.01*scale; From 9ec4f4a634509c97efb3fbf19be00f15c95e6803 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 25 Nov 2024 19:38:40 -0500 Subject: [PATCH 55/80] Allow gendisk2d to create an arbitrary EmpCyl2d basis --- utils/ICs/initial2d.cc | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/utils/ICs/initial2d.cc b/utils/ICs/initial2d.cc index b892d717..a591564c 100644 --- a/utils/ICs/initial2d.cc +++ b/utils/ICs/initial2d.cc @@ -208,6 +208,7 @@ main(int ac, char **av) int nhalo, ndisk; std::string hbods, dbods, suffix, centerfile, halofile1, halofile2; std::string cachefile, config, gentype, dtype, dmodel, mtype, ctype; + std::string diskconf; const std::string mesg("Generates a Monte Carlo realization of a halo with an\n embedded disk using Jeans' equations\n"); @@ -370,6 +371,8 @@ main(int ac, char **av) cxxopts::value(M)->default_value("2")) ("pert", "For testing a quadrupole distribution using test2d", cxxopts::value(pert)->default_value("0.0")) + ("diskconf", "Custom disk configuration file for basis construction", + cxxopts::value(diskconf)) ("allow", "Allow multimass algorithm to generature negative masses for testing") ("nomono", "Allow non-monotonic mass interpolation") ("report", "Print out progress in BiorthCyl table evaluation") @@ -582,16 +585,30 @@ main(int ac, char **av) yml << YAML::Key << "verbose" << YAML::Value << true; // Build the diskconf stanza - yml << YAML::Key << "diskconf" << YAML::BeginMap - << YAML::Key << "name" << YAML::Value << "expon" - << YAML::Key << "parameters" - << YAML::BeginMap - << YAML::Key << "aexp" << YAML::Value << ACYL - << YAML::EndMap - << YAML::EndMap; - - // End the configuration map - yml << YAML::EndMap; + yml << YAML::Key << "diskconf"; + if (vm.count("diskconf")) { + YAML::Node node = YAML::Load(vm["diskconf"].as()); + yml << YAML::Value << node; + } + else { + // Begin the map + yml << YAML::BeginMap + << YAML::Key << "name" << YAML::Value << "expon" + << YAML::Key << "parameters" + << YAML::BeginMap + << YAML::Key << "aexp" << YAML::Value << ACYL + << YAML::EndMap + << YAML::EndMap; + // End the configuration map + yml << YAML::EndMap; + } + + // TEST + std::cout << std::string(80, '-') << std::endl + << "Test output" << std::endl + << std::string(80, '-') << std::endl + << yml.c_str() << std::endl + << std::string(80, '-') << std::endl; // Create expansion only if needed . . . std::shared_ptr expandd; From 5a51669708acaac548dfacd2e4209c506af6a49b Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 25 Nov 2024 19:38:57 -0500 Subject: [PATCH 56/80] Fix scaling for CB disk --- src/CBDisk.H | 7 +++- src/CBDisk.cc | 108 ++++++++++++++++---------------------------------- 2 files changed, 41 insertions(+), 74 deletions(-) diff --git a/src/CBDisk.H b/src/CBDisk.H index 290c4b4a..d072510a 100644 --- a/src/CBDisk.H +++ b/src/CBDisk.H @@ -57,10 +57,15 @@ private: void get_potl_dens(double r, double z, Eigen::MatrixXd& p, Eigen::MatrixXd& d, int tid); - // Parameters + //@{ + //! Parameters double scale; int mmax; string model; + //@} + + //! Potential, force, and density scale factors + double fac1, fac2; //! Valid keys for YAML configurations static const std::set valid_keys; diff --git a/src/CBDisk.cc b/src/CBDisk.cc index 67137cab..b928b43c 100644 --- a/src/CBDisk.cc +++ b/src/CBDisk.cc @@ -45,6 +45,12 @@ CBDisk::CBDisk(Component* c0, const YAML::Node& conf, MixtureBasis* m) : } setup(); + + // Potential, force, and density scaling + // + fac1 = pow(scale, -0.5); + fac2 = pow(scale, -1.5); + if (myid==0) orthoCheck(); } @@ -178,8 +184,8 @@ void CBDisk::get_dpotl(double r, double z, for (int m=0; m<=mmax; m++) { // Pack the array for (int j=0; j ortho(mmax+1); Eigen::VectorXd worst(mmax+1); + Eigen::MatrixXd vpot(mmax+1, nmax), vden(mmax+1, nmax); double Rmax = scale*100.0; + worst.setZero(); - // Direct test + // Allocate result matrices // - worst.setZero(); + for (auto & v : ortho) { + v.resize(nmax, nmax); + v.setZero(); + } - for (int m=0; m<=mmax; m++) { + for (int i=0; i worst(m)) worst(m) = fabs(test); } @@ -424,9 +438,9 @@ void CBDisk::orthoCheck() if (worst(m) > tol) { std::ostringstream sout; - sout << "CBDisk.ortho." << m << ".direct"; + sout << "CBDisk.ortho." << m ; std::ofstream out(sout.str()); - if (out) out << ortho << std::endl; + if (out) out << ortho[m] << std::endl; } } @@ -434,9 +448,6 @@ void CBDisk::orthoCheck() std::cout << hh << std::endl << hh << "Orthogonality check" << std::endl << hh << std::endl - << hh << "--------------" << std::endl - << hh << "Direct test" << std::endl - << hh << "--------------" << std::endl << hh << std::setw(6) << "m" << std::setw(16) << "worst" << std::endl << hh << std::setw(6) << "-" << std::setw(16) << "-----" << std::endl; @@ -444,55 +455,6 @@ void CBDisk::orthoCheck() std::cout << hh << std::setw(6) << m << std::setw(16) << worst(m) << std::endl; std::cout << std::endl; - - // Recursion test - // - worst.setZero(); - - for (int m=0; m<=mmax; m++) { - - ortho.setZero(); - - for (int i=0; i worst(m)) worst(m) = fabs(test); - } - } - - if (worst(m) > tol) { - std::ostringstream sout; - sout << "CBDisk.ortho." << m << ".recurse"; - std::ofstream out(sout.str()); - if (out) out << ortho << std::endl; - } - - } - - std::cout << hh << "--------------" << std::endl - << hh << "Recursion test" << std::endl - << hh << "--------------" << std::endl - << hh << std::setw(6) << "m" << std::setw(16) << "worst" << std::endl - << hh << std::setw(6) << "-" << std::setw(16) << "-----" << std::endl; - for (int m=0; m<=mmax; m++) - std::cout << hh << std::setw(6) << m << std::setw(16) << worst(m) << std::endl; - std::cout << hh << std::endl; - const int numr = 10; double dR = 3.0*scale/numr, dH = 0.01*scale; @@ -503,9 +465,9 @@ void CBDisk::orthoCheck() out << std::setw(4) << m << std::setw(16) << r; for (int j=0; j Date: Tue, 26 Nov 2024 10:37:24 -0500 Subject: [PATCH 57/80] Clean up only --- src/CBDisk.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/CBDisk.cc b/src/CBDisk.cc index b928b43c..193b4ab7 100644 --- a/src/CBDisk.cc +++ b/src/CBDisk.cc @@ -221,12 +221,12 @@ void CBDisk::get_potl_dens(double r, double z, p.resize(mmax+1, nmax); // Resize the return arrays d.resize(mmax+1, nmax); - double R = r/scale, facp = pow(scale, -0.5), facd = pow(scale, -1.5); + double R = r/scale; for (int m=0; m<=mmax; m++) { // Pack the array for (int j=0; j Date: Tue, 26 Nov 2024 10:37:42 -0500 Subject: [PATCH 58/80] Add scaling for CBDisk to pyEXP --- expui/BiorthBasis.H | 3 +++ expui/BiorthBasis.cc | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/expui/BiorthBasis.H b/expui/BiorthBasis.H index ab685859..b5aa9ac8 100644 --- a/expui/BiorthBasis.H +++ b/expui/BiorthBasis.H @@ -607,6 +607,9 @@ namespace BasisClasses void dens(const int m, const double r, Eigen::VectorXd& a); //@} + //! Potential, force, and density scaling + double fac1, fac2; + protected: //! Evaluate basis in cylindrical coordinates diff --git a/expui/BiorthBasis.cc b/expui/BiorthBasis.cc index 50c1a1b4..d91d1142 100644 --- a/expui/BiorthBasis.cc +++ b/expui/BiorthBasis.cc @@ -2086,6 +2086,11 @@ namespace BasisClasses // if (not conf["scale"]) conf["scale"] = 1.0; + // Potential, force, and density scaling + // + fac1 = pow(scale, -0.5); + fac2 = pow(scale, -1.5); + // Orthogonality sanity check // orthoTest(); @@ -2122,9 +2127,10 @@ namespace BasisClasses tab.resize(mmax+1, nmax); Eigen::VectorXd a(nmax); for (int m=0; m<=mmax; m++) { - potl(m, r, a); + potl(m, r/scale, a); tab.row(m) = a; } + tab *= fac1; } // Get density @@ -2133,9 +2139,10 @@ namespace BasisClasses tab.resize(mmax+1, nmax); Eigen::VectorXd a(nmax); for (int m=0; m<=mmax; m++) { - dens(m, r, a); + dens(m, r/scale, a); tab.row(m) = a; } + tab *= fac2; } // Get force @@ -2144,9 +2151,10 @@ namespace BasisClasses tab.resize(mmax+1, nmax); Eigen::VectorXd a(nmax); for (int m=0; m<=mmax; m++) { - dpot(m, r, a); + dpot(m, r/scale, a); tab.row(m) = a; } + tab *= fac1/scale; } // Routines for computing biorthonormal pairs based on @@ -2342,17 +2350,29 @@ namespace BasisClasses double r = lq.knot(i) * Rmax, fac = lq.weight(i) * Rmax; Eigen::VectorXd vpot, vden; - potl(m, r, vpot); - dens(m, r, vden); + potl(m, r/scale, vpot); + dens(m, r/scale, vden); for (int j=0; j Date: Sun, 8 Dec 2024 15:23:27 -0500 Subject: [PATCH 59/80] Remove the default 'diag' output suffix --- utils/ICs/initial2d.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/ICs/initial2d.cc b/utils/ICs/initial2d.cc index a591564c..106d4e7b 100644 --- a/utils/ICs/initial2d.cc +++ b/utils/ICs/initial2d.cc @@ -364,7 +364,7 @@ main(int ac, char **av) ("centerfile", "File containing phase-space center", cxxopts::value(centerfile)) ("suffix", "Suffix for output files", - cxxopts::value(suffix)->default_value("diag")) + cxxopts::value(suffix)) ("threads", "Number of threads to run", cxxopts::value(nthrds)->default_value("1")) ("M,MP", "For testing an m>0 harmonic distribution using test2d", From 9f13a653d7c91baa4c2d6d4f4eb0e823b4862b3e Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Tue, 10 Dec 2024 14:22:57 -0500 Subject: [PATCH 60/80] Use 'find_package' to identify the git location --- CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d365908..cae3c8d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ find_package(HDF5 COMPONENTS C CXX HL REQUIRED) find_package(TIRPC) # Check for alternative Sun rpc support find_package(Eigen3 REQUIRED) find_package(PNG) +find_package(Git) # Check for FE include(FEENABLE) @@ -192,7 +193,7 @@ add_compile_definitions(DATTRIB_CUDA=${CUDA_EXP_DATTRIB}) # Get the current working branch execute_process( - COMMAND git rev-parse --abbrev-ref HEAD + COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_BRANCH OUTPUT_STRIP_TRAILING_WHITESPACE @@ -200,7 +201,7 @@ execute_process( # Git submodule updates execute_process( - COMMAND git submodule update --init --recursive + COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} RESULT_VARIABLE GIT_SUBMOD_RESULT ) @@ -213,7 +214,7 @@ endif() # Get the latest abbreviated commit hash of the working branch execute_process( - COMMAND git rev-parse HEAD + COMMAND ${GIT_EXECUTABLE} rev-parse HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_COMMIT OUTPUT_STRIP_TRAILING_WHITESPACE From febd3b7dd1942611fd77732ea4d4dabae35b338c Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Tue, 17 Dec 2024 16:09:01 -0500 Subject: [PATCH 61/80] Added a spherical histogram1d with logarithmic scaling to pyEXP; since this is a new function, there is no API change --- expui/FieldGenerator.H | 5 ++++ expui/FieldGenerator.cc | 52 +++++++++++++++++++++++++++++++++++++++++ pyEXP/FieldWrappers.cc | 26 +++++++++++++++++++++ 3 files changed, 83 insertions(+) diff --git a/expui/FieldGenerator.H b/expui/FieldGenerator.H index 4ab573ae..d83cc410 100644 --- a/expui/FieldGenerator.H +++ b/expui/FieldGenerator.H @@ -115,6 +115,11 @@ namespace Field histogram1d(PR::PRptr reader, double rmax, int nbins, std::string proj, std::vector center={0.0, 0.0, 0.0}); + //! Compute spherical log histogram from particles + std::tuple + histo1dlog(PR::PRptr reader, double rmin, double rmax, int nbins, + std::vector center={0.0, 0.0, 0.0}); + //! Write field slices to files. This will be VTK your build is //! compiled with VTK and ascii tables otherwise. void file_slices(BasisClasses::BasisPtr basis, CoefClasses::CoefsPtr coefs, diff --git a/expui/FieldGenerator.cc b/expui/FieldGenerator.cc index 73a1595f..b12df759 100644 --- a/expui/FieldGenerator.cc +++ b/expui/FieldGenerator.cc @@ -894,6 +894,58 @@ namespace Field } // END histogram1d + std::tuple + FieldGenerator::histo1dlog(PR::PRptr reader, double rmin, double rmax, + int nbins, std::vector ctr) + { + if (rmin <= 0.0) throw std::runtime_error("FieldGenerator::histo1dlog: rmin must be > 0.0"); + if (rmax <= rmin) throw std::runtime_error("FieldGenerator::histo1dlog: rmax must be > rmin"); + + const double pi = 3.14159265358979323846; + + Eigen::VectorXf rad = Eigen::VectorXf::Zero(nbins); + Eigen::VectorXf ret = Eigen::VectorXf::Zero(nbins); + + double lrmin = log(rmin), lrmax = log(rmax); + double del = (lrmax - lrmin)/nbins; + + // Make the histogram + // + for (auto p=reader->firstParticle(); p!=0; p=reader->nextParticle()) { + double rad = 0.0; + for (int k=0; k<3; k++) { + double pp = p->pos[k] - ctr[k]; + rad += pp*pp; + } + + int indx = floor((log(sqrt(rad)) - lrmin)/del); + if (indx>=0 and indxmass; + } + + // Accumulate between MPI nodes; return value to root node + // + if (use_mpi) { + if (myid==0) + MPI_Reduce(MPI_IN_PLACE, ret.data(), ret.size(), + MPI_FLOAT, MPI_SUM, 0, MPI_COMM_WORLD); + else + MPI_Reduce(ret.data(), NULL, ret.size(), + MPI_FLOAT, MPI_SUM, 0, MPI_COMM_WORLD); + } + + // Compute density + // + double d3 = exp(3.0*del); + double rf = 4.0*pi/3.0*(d3 - 1.0); + for (int i=0; i> FieldGenerator::points(BasisClasses::BasisPtr basis, CoefClasses::CoefsPtr coefs) diff --git a/pyEXP/FieldWrappers.cc b/pyEXP/FieldWrappers.cc index 165e58da..9cfca7e9 100644 --- a/pyEXP/FieldWrappers.cc +++ b/pyEXP/FieldWrappers.cc @@ -304,6 +304,32 @@ void FieldGeneratorClasses(py::module &m) { py::arg("projection"), py::arg("center") = std::vector(3, 0.0)); + f.def("histo1dlog", &Field::FieldGenerator::histo1dlog, + R"( + Make a 1d density histogram (array) in spherical projection with + logarithmic scaling + + Parameters + ---------- + reader : ParticleReader + particle reader instance + rmin : float + minimum radius of the histogram (>0) + rmax : float + linear extent of the histogram + nbins : int + number of bins + center : list(float, float, float), default=[0, 0, 0] + origin for computing the histogram + + Returns + ------- + tuple(numpy.ndarray, numpy.ndarray) + the evaluation grid and computed 1d histogram + )", + py::arg("reader"), py::arg("rmin"), py::arg("rmax"), py::arg("nbins"), + py::arg("center") = std::vector(3, 0.0)); + f.def("file_lines", &Field::FieldGenerator::file_lines, R"( Write field arrays to files using the supplied string prefix. From 46e7b2e0842ad165d2370ba0f26d4a2689256bd0 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 19 Dec 2024 17:25:49 -0500 Subject: [PATCH 62/80] Implement pseudo-forces for 'Orient' moving 'CENTER' non-inertial frames --- expui/BasisFactory.H | 30 +++++++++++++- expui/BasisFactory.cc | 93 +++++++++++++++++++++++++++++++++++++++++++ expui/BiorthBasis.cc | 8 +++- src/Component.H | 37 ++++++++++++++++- src/Component.cc | 16 +++++++- src/Orient.H | 17 ++++++-- src/Orient.cc | 30 ++++++++++++-- src/user/UserBar.cc | 12 +++--- src/user/UserDisk.cc | 6 +-- src/user/UserHalo.cc | 2 +- 10 files changed, 229 insertions(+), 22 deletions(-) diff --git a/expui/BasisFactory.H b/expui/BasisFactory.H index 08988793..965c051c 100644 --- a/expui/BasisFactory.H +++ b/expui/BasisFactory.H @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -123,8 +124,22 @@ namespace BasisClasses //! Midplane escursion parameter double colh = 4.0; - public: + //@ + //! Pseudo-acceleration db + Eigen::VectorXd t_accel; + Eigen::MatrixXd p_accel; + //@} + //! Number of center points in acceleration estimator + int Naccel = 0; + + //! Get the current pseudo acceleration value + Eigen::Vector3d currentAccel(double time); + + public: + //! The current pseudo acceleration + Eigen::Vector3d pseudo {0, 0, 0}; + //! Constructor from YAML node Basis(const YAML::Node& conf, const std::string& name="Basis"); @@ -231,6 +246,19 @@ namespace BasisClasses //! Height above/below the plane for midplane search in disk scale //! lengths void setColumnHeight(double value) { colh = value; } + + //@{ + //! Initialize non-inertial forces + void setNonInertial(int N, Eigen::VectorXd& x, Eigen::MatrixXd& pos); + void setNonInertial(int N, const std::string& orient); + //@} + + //! Set the current pseudo acceleration + void setNonInertialAccel(double time) + { + if (Naccel > 0) pseudo = currentAccel(time); + } + }; using BasisPtr = std::shared_ptr; diff --git a/expui/BasisFactory.cc b/expui/BasisFactory.cc index d3c11304..483dcca4 100644 --- a/expui/BasisFactory.cc +++ b/expui/BasisFactory.cc @@ -283,5 +283,98 @@ namespace BasisClasses return makeFromArray(time); } + void Basis::setNonInertial(int N, Eigen::VectorXd& t, Eigen::MatrixXd& pos) + { + Naccel = N; + t_accel = t; + p_accel = pos; + } + + void Basis::setNonInertial(int N, const std::string& orient) + { + std::ifstream in(orient); + + if (not in) { + throw std::runtime_error("Cannot open Orient file with centering data: " + orient); + } + + const int cbufsiz = 16384; + std::unique_ptr cbuf(new char [cbufsiz]); + + // Look for data and write it while + // accumlating data for averaging + Eigen::Vector3d testread; + double time, dummy; + + std::vector times; + std::vector centers; + + while (in) { + + in.getline(cbuf.get(), cbufsiz); + if (in.rdstate() & (ios::failbit | ios::eofbit)) break; + + // Skip comment lines + // + if (cbuf[0] == '#') continue; + + std::istringstream line(cbuf.get()); + + // Read until current time is reached + line >> time; // + line >> dummy; + line >> dummy; + + bool allRead = true; + for (int i=0; i<8; i++) { + if (line.eof()) allRead = false; + for (int k; k<3; k++) line >> testread(k); + } + if (allRead) { + times.push_back(time); + centers.push_back(testread); + } + } + + // Repack data + Naccel = N; + t_accel.resize(times.size()); + p_accel.resize(times.size(), 3); + for (int i=0; i t_accel(t_accel.size()-1)) { + std::cout << "ERROR: " << time << " is outside of range of the non-inertial DB" + << std::endl; + ret.setZero(); + return ret; + } + else { + int imin = 0; + int imax = std::lower_bound(t_accel.data(), t_accel.data()+t_accel.size(), time) - t_accel.data(); + if (imax > Naccel) imin = imax - Naccel; + + int num = imax - imin + 1; + Eigen::VectorXd t(num); + Eigen::MatrixXd p(num, 3); + for (int i=imin; i<=imax; i++) { + t(i-imin) = t_accel(i); + for (int k=0; k<3; k++) p(i-imin, k) = p_accel(i, k); + } + + for (int k=0; k<3; k++) + ret(k) = 2.0*std::get<0>(QuadLS(t, p.col(k)).coefs()); + } + return ret; + } + } // END namespace BasisClasses diff --git a/expui/BiorthBasis.cc b/expui/BiorthBasis.cc index d91d1142..513c2fd9 100644 --- a/expui/BiorthBasis.cc +++ b/expui/BiorthBasis.cc @@ -592,7 +592,7 @@ namespace BasisClasses double densfac = 1.0/(scale*scale*scale) * 0.25/M_PI; double potlfac = 1.0/scale; - + return {den0 * densfac, // 0 den1 * densfac, // 1 @@ -3653,7 +3653,7 @@ namespace BasisClasses for (int n=0; ngetFields(ps(n, 0), ps(n, 1), ps(n, 2)); // First 6 fields are density and potential, follewed by acceleration - for (int k=0; k<3; k++) accel(n, k) += v[6+k]; + for (int k=0; k<3; k++) accel(n, k) += v[6+k] - basis->pseudo(k); } return accel; @@ -3722,6 +3722,10 @@ namespace BasisClasses // Install coefficients // basis->set_coefs(newcoef); + + // Set non-inertial force + basis->setNonInertialAccel(t); + } SingleTimeAccel::SingleTimeAccel(double t, std::vector mod) diff --git a/src/Component.H b/src/Component.H index 5dfc1ceb..170f1082 100644 --- a/src/Component.H +++ b/src/Component.H @@ -87,6 +87,9 @@ struct loadb_datum ensemble used to determine the paramters. The energy cutoff is dynamically adjusted to achieve this target. + @param nEJwant is the size of the past states used to estimate the + the acceleration of the expansion frame + @param EJx0 is the initial EJ center x-coordinate (default: 0) @param EJy0 is the initial EJ center y-coordinate (default: 0) @@ -399,10 +402,12 @@ public: double *com; //! Center of velocity double *cov; - //! Center of accelration + //! Center of acceleration double *coa; //! Center (e.g. set by Orient) double *center; + //! Pseudo-accleration (set by Orient) + double *accel; //! Angular momentum vector double *angmom; //! Phase space vector @@ -453,6 +458,9 @@ public: //! Target number of particles for average int nEJwant; + //! Target number of states for pseudo-acceleration estimation + int nEJaccel; + //! Initial EJ center //@{ //! x-coord @@ -859,6 +867,15 @@ public: //! Add to accerlation (by component) inline void AddAcc(int i, int j, double val) { + PartMap::iterator tp = particles.find(i); + if (tp == particles.end()) { + throw BadIndexException(i, particles.size(), __FILE__, __LINE__); + } + tp->second->acc[j] += val - accel[j]; + } + + //! Add to accerlation (by component) + inline void AddAccExt(int i, int j, double val) { PartMap::iterator tp = particles.find(i); if (tp == particles.end()) { throw BadIndexException(i, particles.size(), __FILE__, __LINE__); @@ -868,6 +885,15 @@ public: //! Add to acceleration (by array) inline void AddAcc(int i, double *val) { + PartMap::iterator tp = particles.find(i); + if (tp == particles.end()) { + throw BadIndexException(i, particles.size(), __FILE__, __LINE__); + } + for (int k=0; k<3; k++) tp->second->acc[k] += val[k] - accel[k]; + } + + //! Add to acceleration (by array) + inline void AddAccExt(int i, double *val) { PartMap::iterator tp = particles.find(i); if (tp == particles.end()) { throw BadIndexException(i, particles.size(), __FILE__, __LINE__); @@ -877,6 +903,15 @@ public: //! Add to accerlation (by vector) inline void AddAcc(int i, vector& val) { + PartMap::iterator tp = particles.find(i); + if (tp == particles.end()) { + throw BadIndexException(i, particles.size(), __FILE__, __LINE__); + } + for (int k=0; k<3; k++) tp->second->acc[k] += val[k] - accel[k]; + } + + //! Add to accerlation (by vector) + inline void AddAccExt(int i, vector& val) { PartMap::iterator tp = particles.find(i); if (tp == particles.end()) { throw BadIndexException(i, particles.size(), __FILE__, __LINE__); diff --git a/src/Component.cc b/src/Component.cc index b1cab94b..c4fd1365 100644 --- a/src/Component.cc +++ b/src/Component.cc @@ -47,6 +47,7 @@ const std::set Component::valid_keys_parm = "EJ", "nEJkeep", "nEJwant", + "nEJaccel", "EJkinE", "EJext", "EJdiag", @@ -184,6 +185,7 @@ Component::Component(YAML::Node& CONF) EJ = 0; nEJkeep = 100; nEJwant = 500; + nEJaccel = 0; EJkinE = true; EJext = false; EJdiag = false; @@ -320,6 +322,7 @@ void Component::set_default_values() if (!cconf["EJ"]) cconf["EJ"] = EJ; if (!cconf["nEJkeep"]) cconf["nEJkeep"] = nEJkeep; if (!cconf["nEJwant"]) cconf["nEJwant"] = nEJwant; + if (!cconf["nEJaccel"]) cconf["nEJaccel"] = nEJaccel; if (!cconf["EJkinE"]) cconf["EJkinE"] = EJkinE; if (!cconf["EJext"]) cconf["EJext"] = EJext; if (!cconf["EJdiag"]) cconf["EJdiag"] = EJdiag; @@ -683,6 +686,7 @@ Component::Component(YAML::Node& CONF, istream *in, bool SPL) : conf(CONF) EJ = 0; nEJkeep = 100; nEJwant = 500; + nEJaccel = 0; EJkinE = true; EJext = false; EJdiag = false; @@ -795,6 +799,7 @@ void Component::configure(void) std::cout << "Component: eEJ0 is no longer used, Ecurr is computed from the bodies using the expansion directly" << std::endl; if (cconf["nEJkeep" ]) nEJkeep = cconf["nEJkeep" ].as(); if (cconf["nEJwant" ]) nEJwant = cconf["nEJwant" ].as(); + if (cconf["nEJaccel"]) nEJaccel = cconf["nEJaccel"].as(); if (cconf["EJx0" ]) EJx0 = cconf["EJx0" ].as(); if (cconf["EJy0" ]) EJy0 = cconf["EJy0" ].as(); if (cconf["EJz0" ]) EJz0 = cconf["EJz0" ].as(); @@ -916,6 +921,7 @@ void Component::initialize(void) { com = new double [3]; center = new double [3]; + accel = new double [3]; cov = new double [3]; coa = new double [3]; angmom = new double [3]; @@ -929,6 +935,7 @@ void Component::initialize(void) for (int k=0; k<3; k++) { com[k] = center[k] = cov[k] = coa[k] = 0.0; com0[k] = cov0[k] = acc0[k] = angmom[k] = 0.0; + accel[k] = 0.0; } if (com_system) { @@ -1113,6 +1120,7 @@ void Component::initialize(void) if (EJdiag) cout << "Process " << myid << ": about to create Orient with" << " nkeep=" << nEJkeep << " nwant=" << nEJwant + << " naccel=" << nEJaccel << " EJkinE=" << EJkinE << " EJext=" << EJext; @@ -1139,10 +1147,12 @@ void Component::initialize(void) if (EJkinE) EJctl |= Orient::KE; if (EJext) EJctl |= Orient::EXTERNAL; - orient = new Orient(nEJkeep, nEJwant, EJ, EJctl, EJlogfile, EJdT, EJdamp); + orient = new Orient(nEJkeep, nEJwant, nEJaccel, + EJ, EJctl, EJlogfile, EJdT, EJdamp); if (restart && (EJ & Orient::CENTER)) { Eigen::VectorXd::Map(¢er[0], 3) = orient->currentCenter(); + Eigen::VectorXd::Map(&accel [0], 3) = orient->currentAccel(); } else { if (EJlinear) orient -> set_linear(); if (not com_system) { @@ -1238,6 +1248,7 @@ Component::~Component(void) delete [] com; delete [] center; + delete [] accel; delete [] cov; delete [] coa; delete [] angmom; @@ -3111,12 +3122,15 @@ void Component::fix_positions_cpu(unsigned mlevel) if ((EJ & Orient::CENTER) && !EJdryrun) { auto ctr = orient->currentCenter(); + auto acc = orient->currentAccel(); bool ok = true; for (int i=0; i<3; i++) { if (std::isnan(ctr[i])) ok = false; + if (std::isnan(acc[i])) ok = false; } if (ok) { for (int i=0; i<3; i++) center[i] += ctr[i]; + for (int i=0; i<3; i++) accel [i] += acc[i]; } else if (myid==0) { cout << "Orient: center failure, T=" << tnow << ", adjustment skipped" << endl; diff --git a/src/Orient.H b/src/Orient.H index b2bb1fa9..110c9fff 100644 --- a/src/Orient.H +++ b/src/Orient.H @@ -8,6 +8,7 @@ #include #include +#include #include @@ -96,7 +97,7 @@ private: int keep, current; double damp; Eigen::Matrix3d body, orig; - Eigen::Vector3d axis, center, axis1, center1, center0, cenvel0; + Eigen::Vector3d axis, center, axis1, center1, center0, cenvel0, pseudo; double sumX, sumX2; Eigen::Vector3d sumY, sumY2, sumXY, slope, intercept; double sigA, sigC, sigCz; @@ -110,8 +111,10 @@ private: string logfile; bool linear; - vector pos, psa, vel; + std::vector pos, psa, vel; + std::shared_ptr accel; + void accumulate_cpu(double time, Component* c); #if HAVE_LIBCUDA==1 void accumulate_gpu(double time, Component* c); @@ -125,7 +128,7 @@ public: enum ControlFlags {DIAG=1, KE=2, EXTERNAL=4}; //! Constructor - Orient(int number_to_keep, int target, + Orient(int number_to_keep, int target, int Naccel, unsigned orient_flags, unsigned control_flags, string logfile, double dt=0.0, double damping=1.0); @@ -167,6 +170,14 @@ public: //! Return current center Eigen::Vector3d& currentCenter(void) {return center;}; + //! Return current pseudo force + Eigen::Vector3d& currentAccel(void) + { + pseudo.setZero(); + if (accel) return pseudo = accel->accel(); + return pseudo; + } + //! Return variance of axis determination (linear least squares solution) double currentAxisVar(void) {return sigA;} diff --git a/src/Orient.cc b/src/Orient.cc index 676e4079..c865221a 100644 --- a/src/Orient.cc +++ b/src/Orient.cc @@ -36,7 +36,7 @@ void EL3::debug() const } } -Orient::Orient(int n, int nwant, unsigned Oflg, unsigned Cflg, +Orient::Orient(int n, int nwant, int naccel, unsigned Oflg, unsigned Cflg, string Logfile, double dt, double damping) { keep = n; @@ -59,6 +59,8 @@ Orient::Orient(int n, int nwant, unsigned Oflg, unsigned Cflg, cenvel0.setZero(); axis .setZero(); + if (naccel) accel = std::make_shared(naccel); + // Initialize last time to something // in the near infinite past lasttime = -std::numeric_limits::max(); @@ -70,8 +72,7 @@ Orient::Orient(int n, int nwant, unsigned Oflg, unsigned Cflg, // Set up identity body.setIdentity(); orig = body; - - // Check for previous state on + // check for previous state on // a restart int in_ok; std::vector in1(4), in2(4); @@ -124,6 +125,8 @@ Orient::Orient(int n, int nwant, unsigned Oflg, unsigned Cflg, // Look for data and write it while // accumlating data for averaging + Eigen::Vector3d testread; + while (in && restart) { in.getline(cbuffer, cbufsiz); @@ -169,6 +172,14 @@ Orient::Orient(int n, int nwant, unsigned Oflg, unsigned Cflg, if (static_cast(sumsC.size()) > keep) sumsC.pop_front(); } + bool allRead = true; + for (int i=0; i<3; i++) { + if (line.eof()) allRead = false; + for (int k; k<3; k++) line >> testread(k); + } + if (allRead) { + if (accel) accel->add(time, testread); + } } cout << " -- Orient: current log=" << logfile << " backup=" << backupfile << endl; @@ -244,12 +255,15 @@ Orient::Orient(int n, int nwant, unsigned Oflg, unsigned Cflg, << setw(15) << "| X-com(dif)" // 22 << setw(15) << "| Y-com(dif)" // 23 << setw(15) << "| Z-com(dif)" // 24 + << setw(15) << "| X-accel" // 25 + << setw(15) << "| Y-accel" // 26 + << setw(15) << "| Z-accel" // 27 << endl; out.fill('-'); int icnt = 1; out << "# " << setw(13) << icnt++; - for (int i=0; i<23; i++) out << "| " << setw(13) << icnt++; + for (int i=0; i<26; i++) out << "| " << setw(13) << icnt++; out << endl; out.close(); @@ -660,6 +674,10 @@ void Orient::accumulate(double time, Component *c) } else center = center1; + if (accel) { + accel->add(time, center); + } + // Increment initial center according // to user specified velocity center0 += cenvel0*dtime; @@ -732,6 +750,10 @@ void Orient::logEntry(double time, Component *c) // Columns 22 - 24 for (int k=0; k<3; k++) outl << setw(15) << c->com0[k]; + // Columns 25 - 27 + pseudo = accel->accel(); + for (int k=0; k<3; k++) outl << setw(15) << pseudo[k]; + outl << endl; } } diff --git a/src/user/UserBar.cc b/src/user/UserBar.cc index 52773264..db3f6eee 100644 --- a/src/user/UserBar.cc +++ b/src/user/UserBar.cc @@ -478,14 +478,14 @@ void * UserBar::determine_acceleration_and_potential_thread(void * arg) nn = pp * pow(rr/b5, 3.0)/(b5*b5); } - cC->AddAcc(i, 0, - ffac*( 2.0*( xx*cos2p + yy*sin2p)*fac - 5.0*nn*xx ) ); + cC->AddAccExt(i, 0, + ffac*( 2.0*( xx*cos2p + yy*sin2p)*fac - 5.0*nn*xx ) ); - cC->AddAcc(i, 1, - ffac*( 2.0*(-yy*cos2p + xx*sin2p)*fac - 5.0*nn*yy ) ); + cC->AddAccExt(i, 1, + ffac*( 2.0*(-yy*cos2p + xx*sin2p)*fac - 5.0*nn*yy ) ); - cC->AddAcc(i, 2, - ffac*( -5.0*nn*zz ) ); + cC->AddAccExt(i, 2, + ffac*( -5.0*nn*zz ) ); cC->AddPotExt(i, -ffac*pp*fac ); diff --git a/src/user/UserDisk.cc b/src/user/UserDisk.cc index e80dd789..1bee560a 100644 --- a/src/user/UserDisk.cc +++ b/src/user/UserDisk.cc @@ -395,9 +395,9 @@ void * UserDisk::determine_acceleration_and_potential_thread(void * arg) getTable(rr, zz, pot, fr, fz); // Add acceleration by disk - cC->AddAcc(i, 0, amp * fr*xx/(rr+1.0e-10) ); - cC->AddAcc(i, 1, amp * fr*yy/(rr+1.0e-10) ); - cC->AddAcc(i, 2, amp * fz ); + cC->AddAccExt(i, 0, amp * fr*xx/(rr+1.0e-10) ); + cC->AddAccExt(i, 1, amp * fr*yy/(rr+1.0e-10) ); + cC->AddAccExt(i, 2, amp * fz ); // Add external potential cC->AddPotExt(i, pot); diff --git a/src/user/UserHalo.cc b/src/user/UserHalo.cc index 09d774e3..b3b1a2c7 100644 --- a/src/user/UserHalo.cc +++ b/src/user/UserHalo.cc @@ -216,7 +216,7 @@ void * UserHalo::determine_acceleration_and_potential_thread(void * arg) // Add external accerlation for (int k=0; k<3; k++) - cC->AddAcc(i, k, -dpot*pos[k]/(qq[k]*r) ); + cC->AddAccExt(i, k, -dpot*pos[k]/(qq[k]*r) ); // Add external potential cC->AddPotExt(i, pot ); From c2e5b9d4127c899df7ed5c155e40a2e95e46be43 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 19 Dec 2024 22:31:10 -0500 Subject: [PATCH 63/80] Fix mistake in ExternalForce switching for CUDA in ComponentContainer --- src/ComponentContainer.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ComponentContainer.cc b/src/ComponentContainer.cc index 72030cdd..f399a59d 100644 --- a/src/ComponentContainer.cc +++ b/src/ComponentContainer.cc @@ -771,7 +771,7 @@ void ComponentContainer::compute_potential(unsigned mlevel) if (timing) itmr->second.start(); ext->set_multistep_level(mlevel); - if (use_cuda and not c->force->cudaAware()) { + if (use_cuda and not ext->cudaAware()) { #if HAVE_LIBCUDA==1 c->CudaToParticles(); #endif From bdacd4f41b8daa657d944aca4426ab92c4281b5a Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 19 Dec 2024 22:47:37 -0500 Subject: [PATCH 64/80] Missing files for pseudo-force computation from center time series --- include/PseudoAccel.H | 49 ++++++++++++++++++++++++++++++ include/QuadLS.H | 69 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 include/PseudoAccel.H create mode 100644 include/QuadLS.H diff --git a/include/PseudoAccel.H b/include/PseudoAccel.H new file mode 100644 index 00000000..78f3d45d --- /dev/null +++ b/include/PseudoAccel.H @@ -0,0 +1,49 @@ +#ifndef _PseudoAccel_H_ +#define _PseudoAccel_H_ + +#include +#include + +#include + +#include + +class PseudoAccel +{ +private: + + //! Maximum number of centers + unsigned int Nsize; + + //! Queue of center elements + using Elem = std::array; + std::deque queue; + +public: + + //! Construct a pseudo acceleration estimator + PseudoAccel(unsigned int Nsize) : Nsize(Nsize) {} + + //! Add a center element to the pseudo accelration estimator + void add(double t, const Eigen::Vector3d& c) { + queue.push_back({t, c(0), c(1), c(2)}); + if (queue.size() > Nsize) queue.pop_front(); + } + + Eigen::Vector3d accel() + { + std::vector t, x, y, z; + for (auto &e : queue) { + t.push_back(e[0]); + x.push_back(e[1]); + y.push_back(e[2]); + z.push_back(e[3]); + } + + return {2.0*std::get<0>(QuadLS(t, x).coefs()), + 2.0*std::get<0>(QuadLS(t, y).coefs()), + 2.0*std::get<0>(QuadLS(t, z).coefs())}; + } +}; + +#endif diff --git a/include/QuadLS.H b/include/QuadLS.H new file mode 100644 index 00000000..94474551 --- /dev/null +++ b/include/QuadLS.H @@ -0,0 +1,69 @@ +#ifndef _QUADLS_H_ +#define _QUADLS_H_ + +#include +#include +#include + +// Take a pair of data arrays (x, y) and perform a quadratic regression +template +class QuadLS +{ +private: + double _a, _b, _c; + + void fit( const T &x, const T &y ) + { + auto n = x.size(); + + if (n==y.size() and n>0) { + + double sumx=0, sumy=0, sumxy=0, sumx2y=0, sumx2=0, sumx3=0, sumx4=0; + + for (int i=0; i coefs() const + { return std::make_tuple( _a, _b, _c ); } + + //! Evaluate the quadratic function + double operator()( const double x ) const + { return _a * x * x + _b * x + _c; } +}; + +#endif From 46a36df15ab557f6424df5353db51109e876dac2 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Fri, 20 Dec 2024 14:28:31 -0500 Subject: [PATCH 65/80] Initialize Component's 'modified' value --- src/Component.H | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Component.H b/src/Component.H index 170f1082..a0a8c97a 100644 --- a/src/Component.H +++ b/src/Component.H @@ -331,7 +331,7 @@ protected: void seq_new_particles(); //! Particle number update is needed - unsigned modified; + unsigned modified=0; //! No multistep switching bool noswitch; From d2f5f5c388f325c0b39e2174e76f913edd2dd499 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Fri, 20 Dec 2024 14:29:14 -0500 Subject: [PATCH 66/80] Make debug value static to prevent namespace and addressing issues --- include/fpetrap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/fpetrap.h b/include/fpetrap.h index 797512fb..fc511d04 100644 --- a/include/fpetrap.h +++ b/include/fpetrap.h @@ -16,7 +16,7 @@ #endif /// Global to hold error code -volatile int my_err; +static int my_err; /// Very lean error handler. Only good for setting a debugger breakpoint. static void my_fpu_handler(int err) From 64c87ccc18cf2b0e50febf2d9f5f6ec877f1f50c Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Fri, 20 Dec 2024 19:28:00 -0500 Subject: [PATCH 67/80] Removed RKHS stuff and made a new branch for the rest --- expui/CMakeLists.txt | 2 +- expui/KoopmanRKHS.H | 187 ------ expui/KoopmanRKHS.cc | 840 ------------------------ expui/LiouvilleRKHS.H | 284 -------- expui/LiouvilleRKHS.cc | 1424 ---------------------------------------- pyEXP/EDMDWrappers.cc | 322 --------- 6 files changed, 1 insertion(+), 3058 deletions(-) delete mode 100644 expui/KoopmanRKHS.H delete mode 100644 expui/KoopmanRKHS.cc delete mode 100644 expui/LiouvilleRKHS.H delete mode 100644 expui/LiouvilleRKHS.cc diff --git a/expui/CMakeLists.txt b/expui/CMakeLists.txt index 97a41cb5..7e55a366 100644 --- a/expui/CMakeLists.txt +++ b/expui/CMakeLists.txt @@ -38,7 +38,7 @@ endif() set(expui_SOURCES BasisFactory.cc BiorthBasis.cc FieldBasis.cc CoefContainer.cc CoefStruct.cc FieldGenerator.cc expMSSA.cc Coefficients.cc KMeans.cc Centering.cc ParticleIterator.cc - Koopman.cc KoopmanRKHS.cc LiouvilleRKHS.cc BiorthBess.cc) + Koopman.cc BiorthBess.cc) add_library(expui ${expui_SOURCES}) set_target_properties(expui PROPERTIES OUTPUT_NAME expui) target_include_directories(expui PUBLIC ${common_INCLUDE}) diff --git a/expui/KoopmanRKHS.H b/expui/KoopmanRKHS.H deleted file mode 100644 index 30caccba..00000000 --- a/expui/KoopmanRKHS.H +++ /dev/null @@ -1,187 +0,0 @@ -#ifndef EXP_KOOPMAN_RKHS_H -#define EXP_KOOPMAN_RKHS_H - -#include -#include "CoefContainer.H" - -#include - -namespace MSSA -{ - /** - Class for eDMD processing of EXP coefficients along with - user-specified auxiliary channels - */ - class KoopmanRKHS - { - - public: - - //! RKHS types - enum class RKHS - { - Polynomial, - Exponential, - Gaussian - }; - - //! For type reflection and parsing - static std::map RKHS_names; - static std::map RKHS_values; - - protected: - - //@{ - //! Repacked stream data for eDMD - std::map, mSSAkeyCompare > data; - //@} - - //! Coefficient container - CoefContainer coefDB; - - //! Working updated copy of DB - std::shared_ptr newDB; - - //! Parameter database - YAML::Node params; - - //! Primary Koopman analysis - void analysis(); - - bool computed, reconstructed; - - //@{ - //! Test stuff - double lam=0.9, mu=0.5, c=0.0, tscale=1.0; - bool kepler = true, plummer = false, radial = false, oscil = false; - bool testF = true, use_red = true; - //@} - - - //! EDMD modes - Eigen::MatrixXcd Xi; - - //! DMD state matrices - Eigen::MatrixXd G, A, X; - - //! Eigenvalue tolerance - double tol = 1.0e-6; - - //! Max eigenvalue count - int evCount = 100; - - //! Eigenvalues values - Eigen::VectorXd S2, SP; - - //! Koopman eigenvalues - Eigen::VectorXcd SR, SL; - - //! Koopman operator estimate - Eigen::MatrixXd K; - - //! Eigenvectors - Eigen::MatrixXd Q; - - //! Left and right eigenvectors - Eigen::MatrixXcd U, V; - - //! Parameters - //@{ - bool verbose, powerf, project; - std::string prefix, config; - int nev; - //@} - - //! Construct YAML node from string - void assignParameters(const std::string pars); - - //! Number of channels - int nkeys; - - //! Number of points in the time series - int numT; - - //! Valid keys for YAML configurations - static const std::set valid_keys; - - //! RKHS parameters - double d, alpha; - - //! RKHS type - RKHS rkhs = RKHS::Polynomial; - - //! RKHS kernel - double kernel(const Eigen::VectorXd& x, - const Eigen::VectorXd& y); - - public: - - /** Constructor - - @param spec map/dictionary of tuples listing the Coefs object - and a list of keys - - @param flags is a string of YAML with changes for the default ' - flag values - - @param window is the the window length - @param maxEV is the maximum number of eigenvectors - - The map/dictionary has the following structure: - { - "mnemonic1": (Coefs1, [ [key11], [key12], [...], ...]), - "mnemonic2": (Coefs2, [ [key21], [key22], [...], ...]), - . - . - } - - where the mnemonic is choosen for convenience the set of keys - for each coefficient set, Coefs, specify the indices in the - dimensionaly specific the the Coefs instance itself. - E.g. harmonic and radial indicies for spherical and - cylindrical bases. - */ - KoopmanRKHS(const mssaConfig& spec, double tol, int count, const std::string flags=""); - - //! Destructor - virtual ~KoopmanRKHS() {} - - //! Get the eigenvalues - Eigen::VectorXcd eigenvalues() - { - if (not computed) analysis(); - return SR; - } - - //! Return the Koopman modes, the coefficients to the eigenfunctions - Eigen::MatrixXcd getModes() - { - if (not computed) analysis(); - return Xi; - } - - //! Evaluate the contribution from the indexed triple - Eigen::VectorXcd modeEval(int index, const Eigen::VectorXd& x); - - //! Return the eigenfunction - std::complex evecEval(int index, const Eigen::VectorXd& x); - - //! Save current MSSA state to an HDF5 file with the given prefix - void saveState(const std::string& prefix); - - //! Restore current MSSA state to an HDF5 file with the given prefix - void restoreState(const std::string& prefix); - - //! Provides a list of all channel keys - std::vector getAllKeys() - { - std::vector ret; - for (auto v : data) ret.push_back(v.first); - return ret; - } - }; - -} -// END namespace MSSA - -#endif diff --git a/expui/KoopmanRKHS.cc b/expui/KoopmanRKHS.cc deleted file mode 100644 index d84c2aee..00000000 --- a/expui/KoopmanRKHS.cc +++ /dev/null @@ -1,840 +0,0 @@ -#define TESTING -// -// EDMD (Koopman theory) for EXP coefficients -// -// Uses fixed-rank approximation for the SVD to save time and space. -// Uses the approximate SVD randomized algorithm from Halko, -// Martinsson, and Tropp by default. Use BDCSVD or Jacobi flags for -// the standard methods. Using the randomized method together with -// trajectory matrix rather than covariance matrix analysis may lead -// to large errors in eigenvectors. -// -// The implementation here is based on the -// -// M. O. Williams, C. W. Rowley, I. G. Kevrekidis, 2015, "A -// kernel-based method for data-driven Koopman spectral analysis", -// Journal of Computational Dynamics, 2 (2), 247-265 -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include - -/* For debugging - #undef eigen_assert - #define eigen_assert(x) \ - if (!(x)) { throw (std::runtime_error("Eigen assert error")); } -*/ - -#include - -#include - -#include -#include -#include -#include -#include - -namespace MSSA { - - // Helper ostream manipulator for debug coefficient key info - // - std::ostream& operator<< (std::ostream& out, const std::vector& t); - - // Get RKHS name from enum - // - std::map KoopmanRKHS::RKHS_names = - { - {KoopmanRKHS::RKHS::Polynomial, "Polynomial" }, - {KoopmanRKHS::RKHS::Exponential, "Exponential"}, - {KoopmanRKHS::RKHS::Gaussian, "Gaussian" } - }; - - // Get RKHS enum from name - // - std::map KoopmanRKHS::RKHS_values = - { - {"Polynomial", KoopmanRKHS::RKHS::Polynomial }, - {"Exponential", KoopmanRKHS::RKHS::Exponential}, - {"Gaussian", KoopmanRKHS::RKHS::Gaussian } - }; - - - // RKHS kernel - // - double KoopmanRKHS::kernel(const Eigen::VectorXd& x, - const Eigen::VectorXd& y) - { - if (rkhs == RKHS::Polynomial) { - double prod = x.adjoint()*y; - return pow(1.0 + prod/(d*d), alpha); - } else if (rkhs == RKHS::Exponential) { - double prod = x.adjoint()*y; - return exp(prod/(d*d)); - } else if (rkhs == RKHS::Gaussian) { - Eigen::VectorXd diff = x - y; - double prod = diff.adjoint()*diff; - return exp(-prod/(d*d)); - } else { - throw std::runtime_error("KoopmanRKHS: Unknown kernel type"); - } - } - - // Structure for sorting complex numbers and retrieving a permutation index - // - using complex_elem = std::pair, int>; - static - bool complex_comparator(const complex_elem &lhs, const complex_elem &rhs) - { - auto a = lhs.first; - auto b = rhs.first; - - double abs_a = std::abs(a); - double abs_b = std::abs(b); - - // Three step sort: - // (1) By modulus - // (2) By real value - // (3) By imag value - // - if (abs_a == abs_b) { - if (std::real(a) == std::real(b)) - return std::imag(a) < std::imag(b); - else - return std::real(a) < std::real(b); - } else { - return abs_a < abs_b; - } - } - - // Fixed-width matrix element printing - // - static - void matrix_print(std::ostream& out, const Eigen::MatrixXcd& M) - { - for (int i=0; i=0; n--) cumS(n) += cumS(n+1); - bool firstNZ = true; - - SP.resize(S2.size()); // This will be the Penrose - // pseudoinverse - double TOL = S2(S2.size()-1) * tol; - TOL = tol; - nev = 0; // Cache the number of non-zero EVs - for (int n=std::max(0, S2.size()-evCount); n eigensolver2(K); - Eigen::EigenSolver eigensolver3(K.adjoint()); - - // Right eigenvectors of K - // - Eigen::VectorXcd SR2 = eigensolver2.eigenvalues(); - Eigen::MatrixXcd V2 = eigensolver2.eigenvectors(); - - // Left eigenvectors of K - // - Eigen::VectorXcd SL2 = eigensolver3.eigenvalues().conjugate(); - Eigen::MatrixXcd U2 = eigensolver3.eigenvectors(); - - // Assign to class data members - // - U = U2; - V = V2; - - SL = SL2; - SR = SR2; - - // Reorder to match eigenvalues in left and right eigenvectors - // using a comparison functor which sorts by magnitude (modulus), - // real value, and imaginary value by precedence - { - // Right eigenvectors - std::vector v(SR2.size()); - for (int i=0; i u(SL2.size()); - for (int i=0; i - KoopmanRKHS::valid_keys = { - "d", - "alpha", - "kepler", "plummer", "radial", "oscil", "testF", "use_red", "lam", "mu", "c", "tscale", - "kernel", - "verbose", - "output" - }; - - template - std::string get_shape(const Eigen::EigenBase& x) - { - std::ostringstream oss; - oss << "(" << x.rows() << ", " << x.cols() << ")"; - return oss.str(); - } - - Eigen::VectorXcd KoopmanRKHS::modeEval(int index, const Eigen::VectorXd& x) - { - if (not computed) analysis(); - - Eigen::VectorXcd ret(nkeys); - ret.setZero(); - - if (index>=0 and index < numT) { - Eigen::VectorXd Y(numT); - for (int j=0; j Psi = Y.transpose()*Q*SP.asDiagonal()*V.col(index); - - ret = Xi.col(index)*Psi; - } - - return ret; - } - - std::complex KoopmanRKHS::evecEval(int index, const Eigen::VectorXd& x) - { - if (not computed) analysis(); - - static bool first = true; - if (first) { - std::ofstream test("test.data"); - test << "Q" << std::endl << Q << std::endl; - test << "SP" << std::endl << SP << std::endl; - test << "V" << std::endl << V << std::endl; - first = false; - } - - std::complex ret(0.0); - - if (index>=0 and index < numT) { - Eigen::VectorXd Y(numT); - for (int j=0; j()); - - if (params["lam"]) - lam = double(params["lam"].as()); - - if (params["mu"]) - mu = double(params["mu"].as()); - - if (params["c"]) - c = double(params["c"].as()); - - if (params["kepler"]) - kepler = bool(params["kepler"].as()); - - if (params["plummer"]) - plummer = bool(params["plummer"].as()); - - if (params["radial"]) - radial = bool(params["radial"].as()); - - if (params["tscale"]) - tscale = double(params["tscale"].as()); - - if (params["oscil"]) - oscil = bool(params["oscil"].as()); - - if (params["testF"]) - testF = bool(params["testF"].as()); - - if (params["use_red"]) - use_red = bool(params["use_red"].as()); - - if (params["alpha"]) - alpha = double(params["alpha"].as()); - - if (params["kernel"]) { - std::string type = params["kernel"].as(); - if (RKHS_values.find(type) != RKHS_values.end()) - rkhs = RKHS_values[type]; - else - throw std::runtime_error("KoopmanRKHS: kernel type [" + type + - "] not found"); - } - - if (params["verbose"]) - verbose = bool(params["verbose"]); - - if (params["output"]) prefix = params["output"].as(); - else prefix = "exp_rkhs"; - - } - catch (const YAML::ParserException& e) { - std::cout << "KoopmanRKHS::assignParameters, parsing error=" << e.what() - << std::endl; - throw; - } - } - - - // Save current KOOPMAN state to an HDF5 file with the given prefix - void KoopmanRKHS::saveState(const std::string& prefix) - { - if (not computed) return; // No point in saving anything - - if (std::filesystem::exists(prefix + "_rkhs.h5")) { - std::ostringstream sout; - sout << "KoopmanRKHS::saveState: the file <" << prefix + "_edmd.h5" - << "> already exists.\nPlease delete this file or choose a " - << "different file name"; - throw std::runtime_error(sout.str()); - } - - try { - // Create a new hdf5 file - // - HighFive::File file(prefix + "_rkhs.h5", - HighFive::File::ReadWrite | - HighFive::File::Create); - - // Write the time dimension - // - file.createAttribute("numT", HighFive::DataSpace::From(numT)).write(numT); - - // Write the number of channels - // - file.createAttribute("nKeys", HighFive::DataSpace::From(nkeys)).write(nkeys); - - // Write the number of eigenvalues - // - file.createAttribute("nEV", HighFive::DataSpace::From(nev)).write(nev); - - // Save the key list - // - std::vector keylist; - for (auto k : data) keylist.push_back(k.first); - - // Pad keylist entries to maximum key length for HighFive - // - size_t maxSZ = 0; - for (auto v : keylist) maxSZ = std::max(maxSZ, v.size()); - - // The padding value - // - auto padVal = std::numeric_limits::max(); - for (auto & v : keylist) { - if (v.size() < maxSZ) { - for (auto k=v.size(); k keylist; - h5file.getDataSet("keylist").read(keylist); - - // Remove padded values from K5 store - // - auto padVal = std::numeric_limits::max(); - for (auto & v : keylist) { - std::vector::iterator it; - while ((it = std::find(v.begin(), v.end(), padVal)) != v.end()) - v.erase(it); - } - - // Check key list - // - bool bad = false; - for (int n=0; nfirst.size() != keylist[n].size()) bad = true; - else { - for (int i=0; ifirst[i]) bad = true; - } - } - } - - if (bad) { - std::ostringstream sout; - sout << "KoopmanRKHS::restoreState: keylist mismatch." << std::endl - << "Can't restore KoopmanRKHS state! Wanted keylist: "; - for (auto v : data) { - sout << "["; - for (auto u : v.first) sout << u << ' '; - sout << "] "; - } - sout << std::endl << "Found keylist: "; - for (auto v : keylist) { - sout << "["; - for (auto u : v) sout << u << ' '; - sout << "] "; - } - - throw std::runtime_error(sout.str()); - } - - auto analysis = h5file.getGroup("koopmanRKHS_analysis"); - - std::string type = analysis.getDataSet("rkhs").read(); - rkhs = RKHS_values[type]; - - Xi = analysis.getDataSet("Xi" ).read(); - G = analysis.getDataSet("G" ).read(); - A = analysis.getDataSet("A" ).read(); - X = analysis.getDataSet("X" ).read(); - S2 = analysis.getDataSet("S2" ).read(); - SP = analysis.getDataSet("SP" ).read(); - SR = analysis.getDataSet("SR" ).read(); - SL = analysis.getDataSet("SL" ).read(); - Q = analysis.getDataSet("Q" ).read(); - K = analysis.getDataSet("K" ).read(); - U = analysis.getDataSet("U" ).read(); - V = analysis.getDataSet("V" ).read(); - nev = analysis.getDataSet("nev").read(); - - computed = true; - - } catch (HighFive::Exception& err) { - std::cerr << "**** Error opening or reading H5 file ****" << std::endl; - throw; - } - - } - - KoopmanRKHS::KoopmanRKHS(const mssaConfig& config, double tol, int count, - const std::string flags) : tol(tol), evCount(count) - { - // Parse the YAML string - // - assignParameters(flags); - - // Eigen OpenMP reporting - // - static bool firstTime = true; - if (firstTime) { - std::cout << "---- Eigen is using " << Eigen::nbThreads() - << " threads" << std::endl; - firstTime = false; - } - - // Now open and parse the coefficient files - // - coefDB = CoefContainer(config, flags); - - numT = coefDB.times.size(); - - // Generate all the channels - // - for (auto key : coefDB.getKeys()) { - data[key] = coefDB.getData(key); - } - - computed = false; - reconstructed = false; - } - // END KoopmanRKHS constructor - -} -// END namespace MSSA diff --git a/expui/LiouvilleRKHS.H b/expui/LiouvilleRKHS.H deleted file mode 100644 index e5a4d718..00000000 --- a/expui/LiouvilleRKHS.H +++ /dev/null @@ -1,284 +0,0 @@ -#ifndef EXP_LIOUVILLE_RKHS_H -#define EXP_LIOUVILLE_RKHS_H - -#include -#include "CoefContainer.H" - -#include - -namespace MSSA -{ - /** - Class for eDMD processing of trajectories using Liouville opertor - RKHS. This implements both Algorithms 6.1 and 7.1 from the paper - by Joel A. Rosenfeld and Rushikesh Kamalapurkar, "Singular - Dynamic Mode Decomposition", 2023, SIAM J. Appl. Dynamical - Systems, Vol. 22, No. 3, pp 2357-2381. - */ - class LiouvilleRKHS - { - - public: - - //! RKHS types - enum class RKHS - { - Polynomial, - Exponential, - Gaussian - }; - - //! Analysis method - enum class Method - { - Singular, - Eigenfunction - }; - - //! For RKHS type reflection and parsing - static std::map RKHS_names; - static std::map RKHS_values; - - //! For method type reflection and parsing - static std::map Method_names; - static std::map Method_values; - - protected: - - //@{ - //! Repacked stream data for eDMD - std::map data; - //@} - - //! Coefficient container - CoefContainer coefDB; - - //! Working updated copy of DB - std::shared_ptr newDB; - - //! Parameter database - YAML::Node params; - - //! Method - Method method = Method::Eigenfunction; - - //! Primary Liouville analysis - void analysis() - { - switch (method) - { - case Method::Singular: - singular_analysis(); - break; - case Method::Eigenfunction: - eigenfunction_analysis(); - break; - } - } - - //! Singular Louville analysis - void singular_analysis(); - - //! Eigenfunction Liouville analysis - void eigenfunction_analysis(); - - //! Compute G matrix for RKHS space defined by mu value - Eigen::MatrixXd computeGrammian(double mu, double rat=1.0); - - //! Compute occupation-kernel difference matrix - Eigen::MatrixXd computeGammaDiff(double mu); - - //! Compute occupation-kernel vector - Eigen::VectorXd computeGamma(const Eigen::VectorXd& x, double mu); - - //! Compute singular Liouville RKHS matrix - Eigen::MatrixXd computeGramGamma(double mu); - - //! Compute singular Liouville trajectory matrix - Eigen::MatrixXd trajectory(); - - bool computed, reconstructed; - - //@{ - //! Parameters - double mu1=1.0, mu2=1.1, eps=1.0e-4; - bool use_red = true; - //@} - - //! DMD state matrices - Eigen::MatrixXd G1, G2, G3; - - //! Occupation RKHS matrix - Eigen::MatrixXd A; - - //! Occupation kernel matrix - Eigen::MatrixXd O; - - //! Liouville modes for eigenfunction method - Eigen::MatrixXcd Xi; - - //! Liouville modes for singular method - Eigen::MatrixXd Xh; - - //! Eigenbasis - Eigen::MatrixXcd V; - - //! Normalized eigenbasis - Eigen::MatrixXcd Vbar; - - //! Eigenvectors - Eigen::VectorXcd L; - - //! For projection of initial state into eigenfunctions - Eigen::MatrixXcd Ginv; - - //! EDMD modes - Eigen::MatrixXcd Phi; - - //! Eigenvalue tolerance - double tol = 1.0e-6; - - //! Max eigenvalue count - int evCount = 100; - - //! Eigenvalues values - Eigen::VectorXd S1, S2, S3; - - //! Eigenvectors - Eigen::MatrixXd Q1, Q2, Q3; - - //@{ - //! SVD vectors and normalizations - Eigen::MatrixXd UU, VV, V0, Vt; - Eigen::VectorXd SS, Dq, Dp; - //@} - - //! Parameters - //@{ - bool verbose, powerf, project; - std::string prefix, config; - int nev; - //@} - - //! Construct YAML node from string - void assignParameters(const std::string pars); - - //! Number of trajectories - int traj; - - //! Number of active trajectories - int nkeys; - - //! Phase-space rank - int rank; - - //! Number of points in the time series - int numT; - - //! Valid keys for YAML configurations - static const std::set valid_keys; - - //! RKHS parameters - double d, alpha; - - //! RKHS type - RKHS rkhs = RKHS::Polynomial; - - //! RKHS kernels - double kernel(const Eigen::VectorXd& x, - const Eigen::VectorXd& y, - double mu); - - //! Occupation kernel matrix - Eigen::MatrixXd occupation(); - - public: - - /** Constructor - - @param spec map/dictionary of tuples listing the Coefs object - and a list of keys - - @param flags is a string of YAML with changes for the default ' - flag values - - @param window is the the window length - @param maxEV is the maximum number of eigenvectors - - The map/dictionary has the following structure: - { - "mnemonic1": (Coefs1, [ [key11], [key12], [...], ...]), - "mnemonic2": (Coefs2, [ [key21], [key22], [...], ...]), - . - . - } - - where the mnemonic is choosen for convenience the set of keys - for each coefficient set, Coefs, specify the indices in the - dimensionaly specific the the Coefs instance itself. - E.g. harmonic and radial indicies for spherical and - cylindrical bases. - */ - LiouvilleRKHS(const mssaConfig& spec, double tol, int count, const std::string flags=""); - - //! Destructor - virtual ~LiouvilleRKHS() {} - - //! Get the eigenvalues - Eigen::VectorXcd eigenvalues() - { - if (not computed) analysis(); - if (method == Method::Eigenfunction) - return L; - else - return Eigen::VectorXcd(SS); - } - - //! Return the Liouville modes, the coefficients to the eigenfunctions - Eigen::MatrixXcd modeEval() - { - if (method != Method::Eigenfunction) - throw std::runtime_error("LiouvilleRKHS::modeEval is only for the Eigenfunction method"); - - if (not computed) analysis(); - - return Xi; - } - - //! Return the eigenfunctions - Eigen::VectorXcd evecEval(const Eigen::VectorXd& x); - - //! Save current MSSA state to an HDF5 file with the given prefix - void saveState(const std::string& prefix); - - //! Restore current MSSA state to an HDF5 file with the given prefix - void restoreState(const std::string& prefix); - - //! Provides a list of all channel keys - std::vector getAllKeys() - { - std::vector ret; - for (auto v : data) ret.push_back(v.first); - return ret; - } - - //! Switch method: Singular or Eigenfunction - void setMethod(const std::string& method_name) - { - auto it = Method_values.find(method_name); - if (it != Method_values.end()) method = it->second; - else throw std::runtime_error("LiouvilleRKHS::setMethod: unknown method <" + method_name + ">. Expecting or "); - } - - //! Flow vector evaluated at a point - Eigen::VectorXd flow(const Eigen::VectorXd& x); - - //! Compute trajectory for an initial state using RK4 (for singular method only) - Eigen::MatrixXd computeTrajectory(const Eigen::VectorXd& x, double h); - - }; - -} -// END namespace MSSA - -#endif diff --git a/expui/LiouvilleRKHS.cc b/expui/LiouvilleRKHS.cc deleted file mode 100644 index aa9b8524..00000000 --- a/expui/LiouvilleRKHS.cc +++ /dev/null @@ -1,1424 +0,0 @@ -#define TESTING -// -// EDMD (Liouville occupuation kernel version) for EXP coefficients -// -// Uses fixed-rank approximation for the SVD to save time and space. -// Uses the approximate SVD randomized algorithm from Halko, -// Martinsson, and Tropp by default. Use BDCSVD or Jacobi flags for -// the standard methods. Using the randomized method together with -// trajectory matrix rather than covariance matrix analysis may lead -// to large errors in eigenvectors. -// -// The implementation here is based on the -// -// Joel A. Rosenfeld and Rushikesh Kamalapurkar, "Singular Dynamic -// Mode Decomposition", 2023, SIAM J. Appl. Dynamical Systems, -// Vol. 22, No. 3, pp 2357-2381 -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include - -#include - -/* For debugging - #undef eigen_assert - #define eigen_assert(x) \ - if (!(x)) { throw (std::runtime_error("Eigen assert error")); } -*/ - -#include - -#include - -#include -#include -#include -#include -#include - -namespace MSSA { - - // Helper ostream manipulator for debug coefficient key info - // - std::ostream& operator<< (std::ostream& out, const std::vector& t); - - // Get RKHS name from enum - // - std::map LiouvilleRKHS::RKHS_names = - { - {LiouvilleRKHS::RKHS::Polynomial, "Polynomial" }, - {LiouvilleRKHS::RKHS::Exponential, "Exponential"}, - {LiouvilleRKHS::RKHS::Gaussian, "Gaussian" } - }; - - // Get RKHS enum from name - // - std::map LiouvilleRKHS::RKHS_values = - { - {"Polynomial", LiouvilleRKHS::RKHS::Polynomial }, - {"Exponential", LiouvilleRKHS::RKHS::Exponential}, - {"Gaussian", LiouvilleRKHS::RKHS::Gaussian } - }; - - - // Get Method name from enum - // - std::map LiouvilleRKHS::Method_names = - { - {LiouvilleRKHS::Method::Singular, "Singular" }, - {LiouvilleRKHS::Method::Eigenfunction, "Eigenfunction"} - }; - - // Get RKHS enum from name - // - std::map LiouvilleRKHS::Method_values = - { - {"Singular", LiouvilleRKHS::Method::Singular }, - {"Eigenfunction", LiouvilleRKHS::Method::Eigenfunction} - }; - - - // RKHS kernel - // - double LiouvilleRKHS::kernel(const Eigen::VectorXd& x, - const Eigen::VectorXd& y, - double mu) - { - double D2 = d*d/(mu*mu); - - if (rkhs == RKHS::Polynomial) { - double prod = x.adjoint()*y; - return pow(1.0 + prod/D2, alpha); - } else if (rkhs == RKHS::Exponential) { - double prod = x.adjoint()*y; - return exp(prod/D2); - } else if (rkhs == RKHS::Gaussian) { - Eigen::VectorXd diff = x - y; - double prod = diff.adjoint()*diff; - return exp(-prod/D2); - } else { - throw std::runtime_error("LiouvilleRKHS: Unknown kernel type"); - } - } - - // Structure for sorting complex numbers and retrieving a permutation index - // - using complex_elem = std::pair, int>; - static - bool complex_comparator(const complex_elem &lhs, const complex_elem &rhs) - { - auto a = lhs.first; - auto b = rhs.first; - - double abs_a = std::abs(a); - double abs_b = std::abs(b); - - // Three step sort: - // (1) By modulus - // (2) By real value - // (3) By imag value - // - if (abs_a == abs_b) { - if (std::real(a) == std::real(b)) - return std::imag(a) < std::imag(b); - else - return std::real(a) < std::real(b); - } else { - return abs_a < abs_b; - } - } - - // Fixed-width matrix element printing - // - static - void matrix_print(std::ostream& out, const Eigen::MatrixXcd& M) - { - for (int i=0; i eigensolver1(G1 + R, evCount); - S1 = eigensolver1.eigenvalues(); - Q1 = eigensolver1.eigenvectors(); - - RedSVD::RedSymEigen eigensolver2(G2 + R, evCount); - S2 = eigensolver2.eigenvalues(); - Q2 = eigensolver2.eigenvectors(); - } else { - Eigen::SelfAdjointEigenSolver eigensolver1(G1 + R); - if (eigensolver1.info() != Eigen::Success) { - throw std::runtime_error("LiouvilleRKHS: Eigensolver for Grammian 1 failed"); - } - S1 = eigensolver1.eigenvalues(); - Q1 = eigensolver1.eigenvectors(); - - Eigen::SelfAdjointEigenSolver eigensolver2(G2 + R); - if (eigensolver2.info() != Eigen::Success) { - throw std::runtime_error("LiouvilleRKHS: Eigensolver for Grammian 2 failed"); - } - S2 = eigensolver2.eigenvalues(); - Q2 = eigensolver2.eigenvectors(); - } - - // Compute the normalized bases - // - Eigen::VectorXd D0(traj), Dt(traj), D1(traj), D2(traj); - - for (int i=0; i 0.0) D0(i) = 1.0/sqrt(D1(i)); - if (D2(i) > 0.0) Dt(i) = 1.0/sqrt(D2(i)); - } - - V0 = Q1 * D0.asDiagonal(); - Vt = Q2 * Dt.asDiagonal(); - - Eigen::MatrixXd Gq = Vt.transpose() * G2 * Vt; - Eigen::MatrixXd Gp = V0.transpose() * G1 * V0; - - // Compute SVD of PP - // - auto PP = V0.inverse() * Vt; - - if (use_red) { - RedSVD::RedSVD svd(PP, evCount); - UU = svd.matrixU(); - SS = svd.singularValues(); - VV = svd.matrixV(); - } else { - Eigen::BDCSVD - svd(PP, Eigen::ComputeThinU | Eigen::ComputeThinV); - if (svd.info() != Eigen::Success) { - throw std::runtime_error("LiouvilleRKHS::singular_compute: SVD failure"); - } - UU = svd.matrixU(); - SS = svd.singularValues(); - VV = svd.matrixV(); - } - - // Compute the projection of the state into the eigenbasis - // - Dp.resize(traj); - Dq.resize(traj); - -#pragma omp parallel for - for (int i=0; i::infinity(); - double cmax = -std::numeric_limits::infinity(); - auto d = diff.data(); - for (int i=0; i eigensolver1(G2 + R, evCount); - S2 = eigensolver1.eigenvalues(); - Q2 = eigensolver1.eigenvectors(); - - RedSVD::RedSymEigen eigensolver2(G3 + R, evCount); - S3 = eigensolver2.eigenvalues(); - Q3 = eigensolver2.eigenvectors(); - } else { - Eigen::SelfAdjointEigenSolver eigensolver1(G2 + R); - if (eigensolver1.info() != Eigen::Success) { - throw std::runtime_error("LiouvilleRKHS: Eigensolver for Grammian 2 failed"); - } - S2 = eigensolver1.eigenvalues(); - Q2 = eigensolver1.eigenvectors(); - - Eigen::SelfAdjointEigenSolver eigensolver2(G3 + R); - if (eigensolver2.info() != Eigen::Success) { - throw std::runtime_error("LiouvilleRKHS: Eigensolver for Grammian 2 failed"); - } - S3 = eigensolver2.eigenvalues(); - Q3 = eigensolver2.eigenvectors(); - } - - // Compute pseudoinvese of G2 - // - Eigen::MatrixXd G2inv(traj, traj); - Eigen::VectorXd S2inv(traj); - - double TOL = S2(S2.size()-1) * tol; - - for (int n=std::max(0, S2.size()-evCount); n(0, S3.size()-evCount); n eigensolver(PP); - - // Eigenvalues and eigenvectors of PP - // - L = eigensolver.eigenvalues(); - V = eigensolver.eigenvectors(); - - // Compute the normalized eigenbasis - // - Vbar = V; - for (int i=0; i::infinity(); - double cmax = -std::numeric_limits::infinity(); - auto d = diff.data(); - for (int i=0; iflow(y ); - Eigen::VectorXd k2 = hh * this->flow(y + 0.5*k1); - Eigen::VectorXd k3 = hh * this->flow(y + 0.5*k2); - Eigen::VectorXd k4 = hh * this->flow(y + k3 ); - - y += (k1 + 2.0*k2 + 2.0*k3 + k4)/6.0; - } - - return std::tuple(y, t + h); - }; - -#ifdef TESTING - std::cout << "Computing trajectory with " << numT << " X " << rank - << " array" << std::endl; -#endif - - // An array to contain the returned trajectory - Eigen::MatrixXd ret(numT, rank); - - // Compute the time step - double dt = coefDB.times[1] - coefDB.times[0]; - - // Compute the substep number - int substeps = std::max(1, floor(dt/h)); - - // Initial time - double t = coefDB.times[0]; - - // Initial state vector - Eigen::VectorXd y = x; - ret.row(0) = y; - - // Integrate the flow - for (int i=1; i - LiouvilleRKHS::valid_keys = { - "mu1", - "mu2", - "eps", - "d", - "alpha", - "use_red", - "kernel", - "method", - "verbose", - "output" - }; - - template - std::string get_shape(const Eigen::EigenBase& x) - { - std::ostringstream oss; - oss << "(" << x.rows() << ", " << x.cols() << ")"; - return oss.str(); - } - - Eigen::VectorXcd LiouvilleRKHS::evecEval(const Eigen::VectorXd& x) - { - if (method != Method::Eigenfunction) - throw std::runtime_error("LiouvilleRKHS::evecEval make sense for the Eigenfunction method only"); - - if (not computed) analysis(); - - return Vbar.transpose() * computeGamma(x, mu1); - } - - void LiouvilleRKHS::assignParameters(const std::string flags) - { - // Default parameter values - // - d = 1.0; - alpha = 10.0; - verbose = false; - - // Parse the parameters database - // - try { - - // Load parameters from string - // - params = YAML::Load(flags); - - // Check for unmatched keys - // - auto unmatched = YamlCheck(params, valid_keys); - if (unmatched.size()) - throw YamlConfigError("MSSA::LiouvilleRKHS", "parameter", unmatched, __FILE__, __LINE__); - - // Compute flags - // - computed = false; - reconstructed = false; - - // Top level parameter flags - // - if (params["d"]) - d = double(params["d"].as()); - - if (params["eps"]) - eps = double(params["eps"].as()); - - if (params["mu1"]) - mu1 = double(params["mu1"].as()); - - if (params["mu2"]) - mu2 = double(params["mu2"].as()); - - if (params["use_red"]) - use_red = bool(params["use_red"].as()); - - if (params["alpha"]) - alpha = double(params["alpha"].as()); - - if (params["kernel"]) { - std::string type = params["kernel"].as(); - if (RKHS_values.find(type) != RKHS_values.end()) - rkhs = RKHS_values[type]; - else - throw std::runtime_error("LiouvilleRKHS: kernel type [" + type + - "] not found"); - } - - if (params["method"]) { - std::string type = params["method"].as(); - if (Method_values.find(type) != Method_values.end()) - method = Method_values[type]; - else - throw std::runtime_error("LiouvilleRKHS: method type [" + type + - "] not found"); - } - - if (params["verbose"]) - verbose = bool(params["verbose"]); - - if (params["output"]) prefix = params["output"].as(); - else prefix = "exp_rkhs"; - - } - catch (const YAML::ParserException& e) { - std::cout << "LiouvilleRKHS::assignParameters, parsing error=" << e.what() - << std::endl; - throw; - } - } - - - // Save current KOOPMAN state to an HDF5 file with the given prefix - void LiouvilleRKHS::saveState(const std::string& prefix) - { - if (not computed) return; // No point in saving anything - - if (std::filesystem::exists(prefix + "_rkhs.h5")) { - std::ostringstream sout; - sout << "LiouvilleRKHS::saveState: the file <" << prefix + "_edmd.h5" - << "> already exists.\nPlease delete this file or choose a " - << "different file name"; - throw std::runtime_error(sout.str()); - } - - try { - // Create a new hdf5 file - // - HighFive::File file(prefix + "_rkhs.h5", - HighFive::File::ReadWrite | - HighFive::File::Create); - - // Write the time dimension - // - file.createAttribute("numT", HighFive::DataSpace::From(numT)).write(numT); - - // Write the number of channels - // - file.createAttribute("nKeys", HighFive::DataSpace::From(nkeys)).write(nkeys); - - // Write the number of eigenvalues - // - file.createAttribute("nEV", HighFive::DataSpace::From(nev)).write(nev); - - // Hilbert space domain parameter - // - file.createAttribute("mu1", HighFive::DataSpace::From(mu1)).write(mu1); - - // Hilbert space range parameter - // - file.createAttribute("mu2", HighFive::DataSpace::From(mu2)).write(mu2); - - // Regularization parameter - // - file.createAttribute("eps", HighFive::DataSpace::From(eps)).write(eps); - - // The algorithm method - // - std::string mthd = Method_names[method]; - file.createAttribute("method", HighFive::DataSpace::From(mthd)).write(mthd); - - // Save the key list - // - std::vector keylist; - for (auto k : data) keylist.push_back(k.first); - - // Pad keylist entries to maximum key length for HighFive - // - size_t maxSZ = 0; - for (auto v : keylist) maxSZ = std::max(maxSZ, v.size()); - - // The padding value - // - auto padVal = std::numeric_limits::max(); - for (auto & v : keylist) { - if (v.size() < maxSZ) { - for (auto k=v.size(); k 1.0e-12) { - std::ostringstream sout; - sout << "LiouvilleRKHS::restoreState: saved state has mu1=" - << MU1 << " but LiouvilleRKHS expects mu1=" << mu1 - << ".\nCan't restore LiouvilleRKHS state!"; - throw std::runtime_error(sout.str()); - } - - if (fabs(mu2-MU2) > 1.0e-12) { - std::ostringstream sout; - sout << "LiouvilleRKHS::restoreState: saved state has mu2=" - << MU2 << " but LiouvilleRKHS expects mu2=" << mu2 - << ".\nCan't restore LiouvilleRKHS state!"; - throw std::runtime_error(sout.str()); - } - - if (fabs(eps-EPS) > 1.0e-18) { - std::ostringstream sout; - sout << "LiouvilleRKHS::restoreState: saved state has eps=" - << EPS << " but LiouvilleRKHS expects eps=" << eps - << ".\nCan't restore LiouvilleRKHS state!"; - throw std::runtime_error(sout.str()); - } - - if (Method_values[mthd] != method) { - std::ostringstream sout; - sout << "LiouvilleRKHS::restoreState: saved state has method=" - << mthd << " but LiouvilleRKHS expects method=" << Method_names[method] - << ".\nCan't restore LiouvilleRKHS state!"; - throw std::runtime_error(sout.str()); - } - - std::vector keylist; - h5file.getDataSet("keylist").read(keylist); - - // Remove padded values from K5 store - // - auto padVal = std::numeric_limits::max(); - for (auto & v : keylist) { - std::vector::iterator it; - while ((it = std::find(v.begin(), v.end(), padVal)) != v.end()) - v.erase(it); - } - - // Check key list - // - bool bad = false; - for (int n=0; nfirst.size() != keylist[n].size()) bad = true; - else { - for (int i=0; ifirst[i]) bad = true; - } - } - } - - if (bad) { - std::ostringstream sout; - sout << "LiouvilleRKHS::restoreState: keylist mismatch." << std::endl - << "Can't restore LiouvilleRKHS state! Wanted keylist: "; - for (auto v : data) { - sout << "["; - for (auto u : v.first) sout << u << ' '; - sout << "] "; - } - sout << std::endl << "Found keylist: "; - for (auto v : keylist) { - sout << "["; - for (auto u : v) sout << u << ' '; - sout << "] "; - } - - throw std::runtime_error(sout.str()); - } - - auto analysis = h5file.getGroup("koopmanRKHS_analysis"); - - std::string type = analysis.getDataSet("rkhs").read(); - rkhs = RKHS_values[type]; - - if (method == Method::Eigenfunction) { - - G1 = analysis.getDataSet("G1" ).read(); - G2 = analysis.getDataSet("G2" ).read(); - G3 = analysis.getDataSet("G3" ).read(); - A = analysis.getDataSet("A" ).read(); - Vbar = analysis.getDataSet("Vbar").read(); - L = analysis.getDataSet("L" ).read(); - Xi = analysis.getDataSet("Xi" ).read(); - Phi = analysis.getDataSet("Phi" ).read(); - S2 = analysis.getDataSet("S2" ).read(); - S3 = analysis.getDataSet("S3" ).read(); - Q2 = analysis.getDataSet("Q2" ).read(); - Q3 = analysis.getDataSet("Q3" ).read(); - - } else { - - G1 = analysis.getDataSet("G1" ).read(); - G2 = analysis.getDataSet("G2" ).read(); - S1 = analysis.getDataSet("S1" ).read(); - S2 = analysis.getDataSet("S2" ).read(); - Q1 = analysis.getDataSet("Q1" ).read(); - Q2 = analysis.getDataSet("Q2" ).read(); - V0 = analysis.getDataSet("V0" ).read(); - Vt = analysis.getDataSet("Vt" ).read(); - UU = analysis.getDataSet("U" ).read(); - VV = analysis.getDataSet("V" ).read(); - SS = analysis.getDataSet("S" ).read(); - Dp = analysis.getDataSet("Dp" ).read(); - Dq = analysis.getDataSet("Dq" ).read(); - Xh = analysis.getDataSet("Xh" ).read(); - - } - - computed = true; - - } catch (HighFive::Exception& err) { - std::cerr << "**** Error opening or reading H5 file ****" << std::endl; - throw; - } - - } - - LiouvilleRKHS::LiouvilleRKHS(const mssaConfig& config, double tol, int count, - const std::string flags) : tol(tol), evCount(count) - { - // Parse the YAML string - // - assignParameters(flags); - - // Eigen OpenMP reporting - // - static bool firstTime = true; - if (firstTime) { - std::cout << "---- Eigen is using " << Eigen::nbThreads() - << " threads" << std::endl; - firstTime = false; - } - - // Now open and parse the coefficient files - // - coefDB = CoefContainer(config, flags); - - // Number of snapshots - // - numT = coefDB.times.size(); - - // Database names; there should only be one - // - auto names = coefDB.getNames(); - - // Get coefficients for this database - // - auto coefs = coefDB.getCoefs(names[0]); - - // Get the first coefficient set for parameter reflection - // - auto cf = std::dynamic_pointer_cast - (coefs->getCoefStruct(coefDB.times[0])); - - traj = cf->traj; - rank = cf->rank; - - // Generate data store - // - auto keys = coefDB.getKeys(); // For future generalization... - - // Allocate storage and reorder coefficients - // - for (auto key : coefDB.getKeys()) { - data[key].resize(numT, rank); - auto v = coefDB.getData(key); - for (int t=0; t #include -#include -#include namespace py = pybind11; #include @@ -296,324 +294,4 @@ void EDMDtoolkitClasses(py::module &m) { Koopman )"); - py::class_> - g(m, "KoopmanRKHS"); - - g.def(py::init(), - R"( - Koopman RKHS operator approximatation class - - Parameters - ---------- - config : mssaConfig - the input database of components - tol : double - tolerance for eigenvalue decomposition - count : int - top count of eigenvalues - flag : str - YAML stanza of parameter values - - Returns - ------- - Koopman RKHS instance - - Notes - ----- - The configuration should be in the format: - - {'example': (coefs, keylst, [])} - - where keylst is a list of selected PCs. With a SphericalBasis - for example, the keylst should have the format: - - [[l1, m1, n1], [l2, m2, n2], ...] - - Each sublist represents a PC, where l, m, and n are the - spherical harmonics basis parameters. - )", - py::arg("config"), - py::arg("tol"), - py::arg("count"), - py::arg("flags") = ""); - - g.def("eigenvalues", &KoopmanRKHS::eigenvalues, - R"( - Vector of eigenvalues from the EDMD RKHS analysis. - - Returns - ------- - numpy.ndarray - the vector of eigenvalues - - Notes - ----- - Note that these eigenvalues are complex. It may be helpful - to separate the magnitude and phase using Numpy's 'absolute' - and 'angle' functions. - )"); - - g.def("saveState", &KoopmanRKHS::saveState, - R"( - Save current EDMD RKHS state to an HDF5 file with the given prefix - - Parameters - ---------- - prefix : str - output filename prefix - - Returns - ------- - None - )", py::arg("prefix")); - - g.def("restoreState", &KoopmanRKHS::restoreState, - R"( - Restore current EDMD RKHS state from an HDF5 file - - Parameters - ---------- - prefix : str - input filename prefix - - Notes - ----- - The Koopman RKHS instance must be constructed with the same data and - parameters as the saved state. The restoreState routine will check - for the same data dimension and trend state but can not sure - complete consistency. - )", py::arg("prefix")); - - g.def("getModes", &KoopmanRKHS::getModes, - R"( - Get the RKHS mode coefficients for all triples - - Returns - ------- - numpy.ndarray - the RKHS mode coefficients - - See also - -------- - Use in conjunction with 'contributions' to visualize the support - from each EDMD RKHS mode to the coefficient series. - )"); - - g.def("modeEval", &KoopmanRKHS::modeEval, - R"( - Evaluate the contribution from the index triple - - Parameters - ---------- - index : int - the triple index in eigenvalue order - value : ndarray - the input point - - Returns - ------- - numpy.ndarray - the contribution to the trajectory from the indexed triple - - See also - -------- - Use in conjunction with 'contributions' to visualize the support - from each EDMD RKHS mode to the coefficient series. - )", py::arg("index"), py::arg("value")); - - g.def("evecEval", &KoopmanRKHS::evecEval, - R"( - Evaluate the Koopman eigenfunction with given index - - Parameters - ---------- - index : int - the triple index in eigenvalue order - value : ndarray - the input point - - Returns - ------- - numpy.ndarray - the Koopman eigenfunction - - See also - -------- - Use in conjunction with 'contributions' to visualize the support - from each EDMD RKHS mode to the coefficient series. - )", py::arg("index"), py::arg("value")); - - g.def("getAllKeys", &KoopmanRKHS::getAllKeys, - R"( - Provides a list of all internal channel keys (for reference) - - Returns - ------- - list(Key) - list of keys in the format described in config file - - See also - -------- - KoopmanRKHS - )"); - - py::class_> - h(m, "LiouvilleRKHS"); - - h.def(py::init(), - R"( - Liouville RKHS operator approximatation class - - Parameters - ---------- - config : mssaConfig - the input database of components - tol : double - tolerance for eigenvalue decomposition - count : int - top count of eigenvalues - flag : str - YAML stanza of parameter values - - Returns - ------- - Liouville RKHS instance - - Notes - ----- - The configuration should be in the format: - - {'example': (coefs, keylst, [])} - - where keylst is a list of selected PCs. With a SphericalBasis - for example, the keylst should have the format: - - [[l1, m1, n1], [l2, m2, n2], ...] - - Each sublist represents a PC, where l, m, and n are the - spherical harmonics basis parameters. - )", - py::arg("config"), - py::arg("tol"), - py::arg("count"), - py::arg("flags") = ""); - - h.def("eigenvalues", &LiouvilleRKHS::eigenvalues, - R"( - Vector of eigenvalues from the EDMD RKHS analysis. - - Returns - ------- - numpy.ndarray - the vector of eigenvalues - - Notes - ----- - Note that these eigenvalues are complex. It may be helpful - to separate the magnitude and phase using Numpy's 'absolute' - and 'angle' functions. - )"); - - h.def("saveState", &LiouvilleRKHS::saveState, - R"( - Save current EDMD RKHS state to an HDF5 file with the given prefix - - Parameters - ---------- - prefix : str - output filename prefix - - Returns - ------- - None - )", py::arg("prefix")); - - h.def("restoreState", &LiouvilleRKHS::restoreState, - R"( - Restore current EDMD RKHS state from an HDF5 file - - Parameters - ---------- - prefix : str - input filename prefix - - Notes - ----- - The Liouville RKHS instance must be constructed with the same data and - parameters as the saved state. The restoreState routine will check - for the same data dimension and trend state but can not sure - complete consistency. - )", py::arg("prefix")); - - h.def("modeEval", &LiouvilleRKHS::modeEval, - R"( - Get the RKHS mode coefficients for all triples - - Returns - ------- - numpy.ndarray - the RKHS mode coefficients - - See also - -------- - Use in conjunction with 'contributions' to visualize the support - from each EDMD RKHS mode to the coefficient series. - )"); - - h.def("evecEval", &LiouvilleRKHS::evecEval, - R"( - Evaluate the Liouville eigenfunction with given index - - Parameters - ---------- - value : ndarray - the input point - - Returns - ------- - numpy.ndarray - the Liouville eigenfunction - - See also - -------- - Use in conjunction with 'contributions' to visualize the support - from each EDMD RKHS mode to the coefficient series. - )", py::arg("v")); - - h.def("computeTrajectory", &LiouvilleRKHS::computeTrajectory, - R"( - Compute the prediction of the trajectory for singular Liouville - - Parameters - ---------- - value : ndarray - the input point - h : double - internal integration step - - Returns - ------- - numpy.ndarray - the Liouville eigenfunction - - See also - -------- - Use in conjunction with 'contributions' to visualize the support - from each EDMD RKHS mode to the coefficient series. - )", py::arg("v"), py::arg("h")); - - h.def("getAllKeys", &LiouvilleRKHS::getAllKeys, - R"( - Provides a list of all internal channel keys (for reference) - - Returns - ------- - list(Key) - list of keys in the format described in config file - - See also - -------- - LiouvilleRKHS - )"); - } From ffa451378636f7e02e554a8fc92b44a868805a8f Mon Sep 17 00:00:00 2001 From: michael-petersen Date: Sun, 22 Dec 2024 13:35:32 +0000 Subject: [PATCH 68/80] A few more AddAccExt changes --- src/user/UserHalo.cc | 4 ++-- src/user/UserMNdisk.cc | 6 +++--- src/user/UserMW.cc | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/user/UserHalo.cc b/src/user/UserHalo.cc index b3b1a2c7..22f670c5 100644 --- a/src/user/UserHalo.cc +++ b/src/user/UserHalo.cc @@ -128,7 +128,7 @@ void UserHalo::determine_acceleration_and_potential(void) exp_thread_fork(false); - print_timings("UserHalo: accleration timings"); + print_timings("UserHalo: acceleration timings"); } @@ -214,7 +214,7 @@ void * UserHalo::determine_acceleration_and_potential_thread(void * arg) */ // END DEBUG - // Add external accerlation + // Add external acceleration for (int k=0; k<3; k++) cC->AddAccExt(i, k, -dpot*pos[k]/(qq[k]*r) ); diff --git a/src/user/UserMNdisk.cc b/src/user/UserMNdisk.cc index 7aa8b935..f7ceca8c 100644 --- a/src/user/UserMNdisk.cc +++ b/src/user/UserMNdisk.cc @@ -174,9 +174,9 @@ void * UserMNdisk::determine_acceleration_and_potential_thread(void * arg) double fz = -mass*zz*ab/(zb*dn*dn*dn); // Add acceleration by disk - cC->AddAcc(i, 0, amp * fr*xx/(rr+1.0e-10) ); - cC->AddAcc(i, 1, amp * fr*yy/(rr+1.0e-10) ); - cC->AddAcc(i, 2, amp * fz ); + cC->AddAccExt(i, 0, amp * fr*xx/(rr+1.0e-10) ); + cC->AddAccExt(i, 1, amp * fr*yy/(rr+1.0e-10) ); + cC->AddAccExt(i, 2, amp * fz ); // Add external potential cC->AddPotExt(i, pot); diff --git a/src/user/UserMW.cc b/src/user/UserMW.cc index 011c4b17..0c4c36da 100644 --- a/src/user/UserMW.cc +++ b/src/user/UserMW.cc @@ -207,9 +207,9 @@ void * UserMW::determine_acceleration_and_potential_thread(void * arg) double zacc = az1 + az2 + az3 + az4; // Add acceleration to particle - cC->AddAcc(i, 0, xacc ); - cC->AddAcc(i, 1, yacc ); - cC->AddAcc(i, 2, zacc ); + cC->AddAccExt(i, 0, xacc ); + cC->AddAccExt(i, 1, yacc ); + cC->AddAccExt(i, 2, zacc ); // Add external potential cC->AddPotExt(i, pot); From 5a9e677b09963568eba42829ad401c8e2be56d92 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sun, 22 Dec 2024 10:05:39 -0500 Subject: [PATCH 69/80] Added more info on 'git submodule' --- INSTALL.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 449daac4..6bcb7e53 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,16 +1,25 @@ # Configuring and building EXP -We are now using git submodules to provide `yaml-cpp`, which is not -common in the HPC environments. So, from the top-level directory, do -the following: +We are now using git submodules to provide a number of packages that +are not common in the HPC environments. These include + +- `HighFive`, a C++ wrapper +class to HDF5 +- `yaml-cpp`, a C++ class for reading and emitting YAML code +- `pybind11` which is used for Python bindings to the C++ classes +- `rapidxml`, used to write VTK files for rendering outside of EXP +- `png++`, C++ wrappers to the png library [Note: png support is optional] + +CMake will automatically download and configure these packages on the +first call. However, if you would to do this manually, from the +top-level directory, execute the following command: ``` git submodule update --init --recursive ``` -This will install `yaml-cpp` in the `extern` directory. The png++ C++ -wrappers to the png library are also installed in `extern`. Note: png -support is optional. +This will install the source packages in the `extern` directory. + ## EXP uses CMake for building a configuration From ae2ef1963440f7fe62a3f49379fb62458e603cf8 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sun, 22 Dec 2024 10:06:51 -0500 Subject: [PATCH 70/80] Changed 'dump' to 'dumpbasis'. Some doxygen parameter descriptions --- src/CBDisk.H | 13 ++++++++++++- src/Cylinder.cc | 4 ++-- src/FlatDisk.H | 28 +++++++++++++++++++--------- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/CBDisk.H b/src/CBDisk.H index d072510a..dcf68c26 100644 --- a/src/CBDisk.H +++ b/src/CBDisk.H @@ -6,7 +6,18 @@ #include -//! Reference implemenation of the Clutton-Brock disk basis +/** Reference implemenation of the Clutton-Brock disk basis + + Parameters + + \param mmax is the maximum azimuthal order + + \param Lmax alias for mmax + + \param Mmax alias for mmax + + \param scale radius for coordinate scaling. By default, the core radius of the Clutton-Brock density is 1. +*/ class CBDisk : public PolarBasis { diff --git a/src/Cylinder.cc b/src/Cylinder.cc index 73426b89..80eecc83 100644 --- a/src/Cylinder.cc +++ b/src/Cylinder.cc @@ -107,7 +107,7 @@ Cylinder::valid_keys = { "playback", "coefCompute", "coefMaster", - "dump" + "dumpbasis" }; Cylinder::Cylinder(Component* c0, const YAML::Node& conf, MixtureBasis *m) : @@ -553,7 +553,7 @@ void Cylinder::initialize() } } - if (conf["dump"]) dump_basis = true; + if (conf["dumpbasis"]) dump_basis = true; } catch (YAML::Exception & error) { diff --git a/src/FlatDisk.H b/src/FlatDisk.H index e5eeb760..81c7b4eb 100644 --- a/src/FlatDisk.H +++ b/src/FlatDisk.H @@ -16,8 +16,7 @@ typedef std::shared_ptr CylPtr; /** Computes the potential, acceleration and density using an empirical function analysis for a flat Bessel basis and a target density - \param acyltbl is the scale length of target density (default is - 0.6) + Parameters \param rcylmin is the minimum value in the table for the radial basis functions (default is 0.0) @@ -28,22 +27,33 @@ typedef std::shared_ptr CylPtr; \param scale is the expansion factor to get the physical scale from the internal scale (default is 0.01) + \param mmax is the maximum azimuthal order + \param numx is the number of grid points in the scaled radial direction \param numy is the number of grid points in the scaled vertical direction - \param numr is the number radial coordinate knots in the table - (default is 1000) - - \param nfid is the number of basis elements used in the EOF computation - \param knots is the number of quadrature points for EmpCyl2d \param logr scales the EmpCyl2d grid logarithmically - \param model name for EmpCyl2d (default: expon) + \param model name for conditioning the new basis using EmpCyl2d (default: expon) + + \param biorth is the biorthogonal basis set used by EmpCyl2d for conditioning (default: bess) + + \param background sets a fixed monopole potential and force for the model. Used for stability analysis of disks with active and inactive tapers, following Zang + + \param nmaxfid is the maximum radial order for the basis used by EmpCyl2d for conditioning + + \param numr is the number of grid points in the radial direction for the numerical basis in EmpCyl2d + + \param NQDHT is the number of grid points for the numerical Hankel transform + + \param diskconf is a YAML configuration for the target disk model in EmpCyl2d + + \param cachename is the name of the cache file for the newly constructed basis - \param biorth set for EmpCyl2d (default: bess) + \param dumpbasis provides the user with a ascii table of the basis potential-density pairs */ class FlatDisk : public PolarBasis { From 1159c2c19fed179c1c8df0c664438458cb9f712c Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sun, 22 Dec 2024 10:07:17 -0500 Subject: [PATCH 71/80] Changed 'dump' to 'dumpbasis' --- src/FlatDisk.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/FlatDisk.cc b/src/FlatDisk.cc index 8ac4ab0c..706bd95b 100644 --- a/src/FlatDisk.cc +++ b/src/FlatDisk.cc @@ -8,22 +8,22 @@ const std::set FlatDisk::valid_keys = { - "nmaxfid", - "numr", "rcylmin", "rcylmax", "mmax", "numx", "numy", - "NQDHT", "knots", "logr", "model", "biorth", - "diskconf", "background", + "nmaxfid", + "numr", + "NQDHT", + "diskconf", "cachename", - "dump" + "dumpbasis" }; FlatDisk::FlatDisk(Component* c0, const YAML::Node& conf, MixtureBasis* m) : @@ -90,7 +90,7 @@ void FlatDisk::initialize() if (conf["mmax"]) Lmax = Mmax = mmax; // Override base-class values - if (conf["dump"]) dump_basis = conf["dump"].as(); + if (conf["dumpbasis"]) dump_basis = conf["dumpbasis"].as(); } catch (YAML::Exception & error) { if (myid==0) std::cout << "Error parsing parameters in FlatDisk: " From 872edf1c96ef29831f435ea7b54fece51aa07f90 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sun, 22 Dec 2024 10:07:37 -0500 Subject: [PATCH 72/80] One more AddAccExt change --- src/user/UserLogPot.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/user/UserLogPot.cc b/src/user/UserLogPot.cc index 7f34c3af..cf17d6e5 100644 --- a/src/user/UserLogPot.cc +++ b/src/user/UserLogPot.cc @@ -105,11 +105,11 @@ void * UserLogPot::determine_acceleration_and_potential_thread(void * arg) zz = cC->Pos(i, 2); rr = R*R + xx*xx + yy*yy/(b*b) + zz*zz/(c*c); - cC->AddAcc(i, 0,-v2*xx/rr ); + cC->AddAccExt(i, 0,-v2*xx/rr ); - cC->AddAcc(i, 1, -v2*yy/(rr*b*b) ); + cC->AddAccExt(i, 1, -v2*yy/(rr*b*b) ); - cC->AddAcc(i, 2, -v2*zz/(rr*c*c) ); + cC->AddAccExt(i, 2, -v2*zz/(rr*c*c) ); cC->AddPotExt(i, 0.5*v2*log(rr) ); } From 1f3e51e07ed6b8685aa0e3192d1edf3b80d5abd5 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Sun, 22 Dec 2024 15:01:28 -0500 Subject: [PATCH 73/80] Restore SVD options for the Koopman member in expMSSA --- expui/expMSSA.cc | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/expui/expMSSA.cc b/expui/expMSSA.cc index 7a19c6f6..8940d98c 100644 --- a/expui/expMSSA.cc +++ b/expui/expMSSA.cc @@ -1606,38 +1606,36 @@ namespace MSSA { n++; } - double Scale = Y1.norm(); - + // double Scale = Y1.norm(); // auto YY = Y1/Scale; + auto YY = Y1; // Use one of the built-in Eigen3 algorithms // - /* - if (params["Jacobi"]) { + if (params["Jacobi"]) { // -->Using Jacobi Eigen::JacobiSVD - svd(YY, Eigen::ComputeThinU | Eigen::ComputeThinV); + svd(YY, Eigen::ComputeThinU | Eigen::ComputeThinV); S1 = svd.singularValues(); V1 = svd.matrixV(); - } else if (params["BDCSVD"]) { - */ + } // -->Using BDC + else if (params["BDCSVD"]) { Eigen::BDCSVD svd(YY, Eigen::ComputeFullU | Eigen::ComputeFullV); // svd(YY, Eigen::ComputeThinU | Eigen::ComputeThinV); S1 = svd.singularValues(); V1 = svd.matrixV(); - /* - } else { - // -->Use Random approximation algorithm from Halko, Martinsson, - // and Tropp + } + // -->Use Random approximation algorithm from Halko, Martinsson, + // and Tropp + else { int srank = std::min(YY.cols(), YY.rows()); RedSVD::RedSVD svd(YY, srank); S1 = svd.singularValues(); V1 = svd.matrixV(); - } - */ + } std::cout << "shape V1 = " << V1.rows() << " x " << V1.cols() << std::endl; @@ -1680,19 +1678,17 @@ namespace MSSA { // SVD // Use one of the built-in Eigen3 algorithms // - /* + // -->Using Jacobi if (params["Jacobi"]) { - // -->Using Jacobi Eigen::JacobiSVD // svd(VT1, Eigen::ComputeThinU | Eigen::ComputeThinV); svd(VT1, Eigen::ComputeFullU | Eigen::ComputeFullV); SS = svd.singularValues(); UU = svd.matrixU(); VV = svd.matrixV(); - } else if (params["BDCSVD"]) { - */ - { - // -->Using BDC + } + // -->Using BDC + else if (params["BDCSVD"]) { Eigen::BDCSVD // svd(VT1, Eigen::ComputeThinU | Eigen::ComputeThinV); svd(VT1, Eigen::ComputeFullU | Eigen::ComputeFullV); @@ -1700,17 +1696,15 @@ namespace MSSA { UU = svd.matrixU(); VV = svd.matrixV(); } - /* - } else { - // -->Use Random approximation algorithm from Halko, Martinsson, - // and Tropp + // -->Use Random approximation algorithm from Halko, Martinsson, + // and Tropp + else { // RedSVD::RedSVD svd(VT1, std::min(rank, numK-1)); RedSVD::RedSVD svd(VT1, std::max(VT1.rows(), VT2.cols())); SS = svd.singularValues(); UU = svd.matrixU(); VV = svd.matrixV(); } - */ if (out) out << "Singular values" << std::endl << SS << std::endl; From a77784eba1b7870617e21cb63116c3556d2b1827 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 23 Dec 2024 12:28:40 -0500 Subject: [PATCH 74/80] Implementation of the pseudo-force estimator for Orient::Axis - parallels the method for computing the center motion - enhances the PseudoAccel class - adds a new member function to Component needed for Coriolis, Euler, and Centrifugal forces - makes the intent clear when we get to testing the algorithm --- include/PseudoAccel.H | 74 +++++++++++++++++++++++++++++++++---------- src/Component.H | 19 ++++++++--- src/Component.cc | 26 +++++++++++---- src/Orient.H | 15 +++++---- src/Orient.cc | 31 +++++++++++++----- 5 files changed, 124 insertions(+), 41 deletions(-) diff --git a/include/PseudoAccel.H b/include/PseudoAccel.H index 78f3d45d..f3a71240 100644 --- a/include/PseudoAccel.H +++ b/include/PseudoAccel.H @@ -12,37 +12,79 @@ class PseudoAccel { private: - //! Maximum number of centers + //! Maximum number of time points unsigned int Nsize; - //! Queue of center elements - using Elem = std::array; + //! Type flags + bool CENTER=false, AXIS=false; + + //! Queue of elements + using Elem = std::array; std::deque queue; + //! Storage + Eigen::Vector3d accel = Eigen::Vector3d::Zero(); + Eigen::Vector3d omega = Eigen::Vector3d::Zero(); + Eigen::Vector3d domdt = Eigen::Vector3d::Zero(); + public: //! Construct a pseudo acceleration estimator - PseudoAccel(unsigned int Nsize) : Nsize(Nsize) {} + PseudoAccel(unsigned int Nsize, bool center, bool axis) : + Nsize(Nsize), CENTER(center), AXIS(axis) {} //! Add a center element to the pseudo accelration estimator - void add(double t, const Eigen::Vector3d& c) { - queue.push_back({t, c(0), c(1), c(2)}); + void add(double t, const Eigen::Vector3d& c, const Eigen::Vector3d& a) { + queue.push_back({t, c(0), c(1), c(2), a(0), a(1), a(2)}); if (queue.size() > Nsize) queue.pop_front(); } - Eigen::Vector3d accel() + std::tuple operator()() { - std::vector t, x, y, z; - for (auto &e : queue) { - t.push_back(e[0]); - x.push_back(e[1]); - y.push_back(e[2]); - z.push_back(e[3]); + if (CENTER or AXIS) { + + std::vector t, x, y, z, u, v, w; + for (auto &e : queue) { + t.push_back(e[0]); + if (CENTER) { + x.push_back(e[1]); + y.push_back(e[2]); + z.push_back(e[3]); + } + if (AXIS) { + u.push_back(e[4]); + v.push_back(e[5]); + w.push_back(e[6]); + } + } + + if (CENTER) { + accel << + 2.0*std::get<0>(QuadLS(t, x).coefs()), + 2.0*std::get<0>(QuadLS(t, y).coefs()), + 2.0*std::get<0>(QuadLS(t, z).coefs()); + } + + if (AXIS) { + auto [a, b, c] = QuadLS(t, u).coefs(); + auto [d, e, f] = QuadLS(t, v).coefs(); + auto [g, h, i] = QuadLS(t, w).coefs(); + + double T = t.back(); // Last eval time + + Eigen::Vector3d n, dndt, d2ndt2; + n << a*T*T+b*T+c, d*T*T+e*T+f, g*T*T+h*T+i; + dndt << 2.0*a*T+b, 2.0*d*T+e, 2.0*g*T+h; + d2ndt2 << 2.0*a, 2.0*d, 2.0*g; + + // Get angular acceleration and its derivative + omega = n.cross(dndt); + domdt = n.cross(d2ndt2); + } + } - return {2.0*std::get<0>(QuadLS(t, x).coefs()), - 2.0*std::get<0>(QuadLS(t, y).coefs()), - 2.0*std::get<0>(QuadLS(t, z).coefs())}; + return {accel, omega, domdt}; } }; diff --git a/src/Component.H b/src/Component.H index a0a8c97a..82eae2ce 100644 --- a/src/Component.H +++ b/src/Component.H @@ -406,14 +406,18 @@ public: double *coa; //! Center (e.g. set by Orient) double *center; - //! Pseudo-accleration (set by Orient) - double *accel; //! Angular momentum vector double *angmom; //! Phase space vector double *ps; //@} + //@{ + //! Pseudo-accleration (set by Orient) + Eigen::Vector3d accel, omega, domdt, pseudo; + Eigen::Vector3d& getPseudoAccel(double* pos, double* vel); + //@} + /** @name Center of mass system coordinates */ @@ -871,7 +875,8 @@ public: if (tp == particles.end()) { throw BadIndexException(i, particles.size(), __FILE__, __LINE__); } - tp->second->acc[j] += val - accel[j]; + auto acc = getPseudoAccel(tp->second->pos, tp->second->vel); + tp->second->acc[j] += val - acc[j]; } //! Add to accerlation (by component) @@ -889,7 +894,9 @@ public: if (tp == particles.end()) { throw BadIndexException(i, particles.size(), __FILE__, __LINE__); } - for (int k=0; k<3; k++) tp->second->acc[k] += val[k] - accel[k]; + auto acc = getPseudoAccel(tp->second->pos, tp->second->vel); + for (int k=0; k<3; k++) + tp->second->acc[k] += val[k] - acc[k]; } //! Add to acceleration (by array) @@ -907,7 +914,9 @@ public: if (tp == particles.end()) { throw BadIndexException(i, particles.size(), __FILE__, __LINE__); } - for (int k=0; k<3; k++) tp->second->acc[k] += val[k] - accel[k]; + auto acc = getPseudoAccel(tp->second->pos, tp->second->vel); + for (int k=0; k<3; k++) + tp->second->acc[k] += val[k] - acc[k]; } //! Add to accerlation (by vector) diff --git a/src/Component.cc b/src/Component.cc index c4fd1365..89f1c440 100644 --- a/src/Component.cc +++ b/src/Component.cc @@ -921,7 +921,6 @@ void Component::initialize(void) { com = new double [3]; center = new double [3]; - accel = new double [3]; cov = new double [3]; coa = new double [3]; angmom = new double [3]; @@ -1152,7 +1151,7 @@ void Component::initialize(void) if (restart && (EJ & Orient::CENTER)) { Eigen::VectorXd::Map(¢er[0], 3) = orient->currentCenter(); - Eigen::VectorXd::Map(&accel [0], 3) = orient->currentAccel(); + std::tie(accel, omega, domdt) = orient->currentAccel(); } else { if (EJlinear) orient -> set_linear(); if (not com_system) { @@ -1248,7 +1247,6 @@ Component::~Component(void) delete [] com; delete [] center; - delete [] accel; delete [] cov; delete [] coa; delete [] angmom; @@ -3122,15 +3120,13 @@ void Component::fix_positions_cpu(unsigned mlevel) if ((EJ & Orient::CENTER) && !EJdryrun) { auto ctr = orient->currentCenter(); - auto acc = orient->currentAccel(); + std::tie(accel, omega, domdt) = orient->currentAccel(); bool ok = true; for (int i=0; i<3; i++) { if (std::isnan(ctr[i])) ok = false; - if (std::isnan(acc[i])) ok = false; } if (ok) { for (int i=0; i<3; i++) center[i] += ctr[i]; - for (int i=0; i<3; i++) accel [i] += acc[i]; } else if (myid==0) { cout << "Orient: center failure, T=" << tnow << ", adjustment skipped" << endl; @@ -3960,3 +3956,21 @@ void Component::AddPart(PartPtr p) nbodies = particles.size(); } +Eigen::Vector3d& Component::getPseudoAccel(double* pos, double* vel) +{ + pseudo.setZero(); + + if (EJ & Orient::CENTER) { + pseudo += accel; + } + + if (EJ & Orient::AXIS) { + Eigen::Map P(pos); + Eigen::Map V(vel); + + // Coriolis + Euler + Centrifugal forces + pseudo += 2.0*omega.cross(V) + domdt.cross(P) + omega.cross(omega.cross(P)); + } + + return pseudo; +} diff --git a/src/Orient.H b/src/Orient.H index 110c9fff..67682d43 100644 --- a/src/Orient.H +++ b/src/Orient.H @@ -97,7 +97,8 @@ private: int keep, current; double damp; Eigen::Matrix3d body, orig; - Eigen::Vector3d axis, center, axis1, center1, center0, cenvel0, pseudo; + Eigen::Vector3d axis, center, axis1, center1, center0, cenvel0; + Eigen::Vector3d pseudo, omega, domdt; double sumX, sumX2; Eigen::Vector3d sumY, sumY2, sumXY, slope, intercept; double sigA, sigC, sigCz; @@ -170,12 +171,14 @@ public: //! Return current center Eigen::Vector3d& currentCenter(void) {return center;}; - //! Return current pseudo force - Eigen::Vector3d& currentAccel(void) + //! Return current pseudo force, angular velocity, and angular + //! velocity derivative + const std::tuple + currentAccel(void) { - pseudo.setZero(); - if (accel) return pseudo = accel->accel(); - return pseudo; + if (accel) return (*accel)(); + pseudo.setZero(); // No acceleration or angular velocity + return {pseudo, pseudo, pseudo}; } //! Return variance of axis determination (linear least squares solution) diff --git a/src/Orient.cc b/src/Orient.cc index c865221a..bb0da365 100644 --- a/src/Orient.cc +++ b/src/Orient.cc @@ -59,7 +59,10 @@ Orient::Orient(int n, int nwant, int naccel, unsigned Oflg, unsigned Cflg, cenvel0.setZero(); axis .setZero(); - if (naccel) accel = std::make_shared(naccel); + // Instantiate the pseudo-force helper + // class + if (naccel) accel = std::make_shared + (naccel, oflags & CENTER, oflags & AXIS); // Initialize last time to something // in the near infinite past @@ -125,8 +128,6 @@ Orient::Orient(int n, int nwant, int naccel, unsigned Oflg, unsigned Cflg, // Look for data and write it while // accumlating data for averaging - Eigen::Vector3d testread; - while (in && restart) { in.getline(cbuffer, cbufsiz); @@ -175,10 +176,10 @@ Orient::Orient(int n, int nwant, int naccel, unsigned Oflg, unsigned Cflg, bool allRead = true; for (int i=0; i<3; i++) { if (line.eof()) allRead = false; - for (int k; k<3; k++) line >> testread(k); + for (int k; k<3; k++) line >> pseudo(k); } if (allRead) { - if (accel) accel->add(time, testread); + if (accel) accel->add(time, pseudo, axis1); } } @@ -258,12 +259,18 @@ Orient::Orient(int n, int nwant, int naccel, unsigned Oflg, unsigned Cflg, << setw(15) << "| X-accel" // 25 << setw(15) << "| Y-accel" // 26 << setw(15) << "| Z-accel" // 27 + << setw(15) << "| Omega_X" // 28 + << setw(15) << "| Omega_Y" // 29 + << setw(15) << "| Omega_Z" // 30 + << setw(15) << "| dOmega/dt_X" // 31 + << setw(15) << "| dOmega/dt_Y" // 32 + << setw(15) << "| dOmega/dt_Z" // 33 << endl; out.fill('-'); int icnt = 1; out << "# " << setw(13) << icnt++; - for (int i=0; i<26; i++) out << "| " << setw(13) << icnt++; + for (int i=0; i<32; i++) out << "| " << setw(13) << icnt++; out << endl; out.close(); @@ -674,8 +681,10 @@ void Orient::accumulate(double time, Component *c) } else center = center1; + // Add to pseudo-acceleration estimator + // if (accel) { - accel->add(time, center); + accel->add(time, center, axis); } // Increment initial center according @@ -750,10 +759,16 @@ void Orient::logEntry(double time, Component *c) // Columns 22 - 24 for (int k=0; k<3; k++) outl << setw(15) << c->com0[k]; + std::tie(pseudo, omega, domdt) = (*accel)(); // Columns 25 - 27 - pseudo = accel->accel(); for (int k=0; k<3; k++) outl << setw(15) << pseudo[k]; + // Columns 28 - 30 + for (int k=0; k<3; k++) outl << setw(15) << omega[k]; + + // Columns 31 - 33 + for (int k=0; k<3; k++) outl << setw(15) << domdt[k]; + outl << endl; } } From 667d6416cf81b9ad5369899ff2863578e0d7a25b Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 23 Dec 2024 14:42:41 -0500 Subject: [PATCH 75/80] Do not apply pseudo force if EJdryrun=True, although the values will be computed and recorded in the orient log file --- src/Component.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Component.cc b/src/Component.cc index 89f1c440..b52f30c3 100644 --- a/src/Component.cc +++ b/src/Component.cc @@ -3960,6 +3960,8 @@ Eigen::Vector3d& Component::getPseudoAccel(double* pos, double* vel) { pseudo.setZero(); + if (EJdryrun) return pseudo; + if (EJ & Orient::CENTER) { pseudo += accel; } From e3459398147ecdd17ea4c2c1345a7f7c730f81cb Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 23 Dec 2024 14:43:39 -0500 Subject: [PATCH 76/80] Don't call for a pseudoforce in output diagnostics if not instantiated --- src/Orient.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Orient.cc b/src/Orient.cc index bb0da365..8c7c2597 100644 --- a/src/Orient.cc +++ b/src/Orient.cc @@ -60,7 +60,7 @@ Orient::Orient(int n, int nwant, int naccel, unsigned Oflg, unsigned Cflg, axis .setZero(); // Instantiate the pseudo-force helper - // class + // class if naccel>0 if (naccel) accel = std::make_shared (naccel, oflags & CENTER, oflags & AXIS); @@ -759,7 +759,8 @@ void Orient::logEntry(double time, Component *c) // Columns 22 - 24 for (int k=0; k<3; k++) outl << setw(15) << c->com0[k]; - std::tie(pseudo, omega, domdt) = (*accel)(); + if (accel) std::tie(pseudo, omega, domdt) = (*accel)(); + // Columns 25 - 27 for (int k=0; k<3; k++) outl << setw(15) << pseudo[k]; From 3c91cbcea9cdf2b3cbc1de0a2b20240908cee15d Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Mon, 23 Dec 2024 14:43:42 -0500 Subject: [PATCH 77/80] Don't call for a pseudoforce in output diagnostics if not instantiated --- src/Orient.H | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Orient.H b/src/Orient.H index 67682d43..bb106bd5 100644 --- a/src/Orient.H +++ b/src/Orient.H @@ -98,7 +98,9 @@ private: double damp; Eigen::Matrix3d body, orig; Eigen::Vector3d axis, center, axis1, center1, center0, cenvel0; - Eigen::Vector3d pseudo, omega, domdt; + Eigen::Vector3d pseudo = Eigen::Vector3d::Zero(); + Eigen::Vector3d omega = Eigen::Vector3d::Zero(); + Eigen::Vector3d domdt = Eigen::Vector3d::Zero(); double sumX, sumX2; Eigen::Vector3d sumY, sumY2, sumXY, slope, intercept; double sigA, sigC, sigCz; From 2c1b3b1f179aabfb8ebd9b3e567410b97df0dca2 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 26 Dec 2024 17:53:37 -0500 Subject: [PATCH 78/80] Add axis to diagnostic output. PseudoAccel should add the original data, not the linear estimate, oops --- src/Orient.cc | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Orient.cc b/src/Orient.cc index 8c7c2597..99597dc2 100644 --- a/src/Orient.cc +++ b/src/Orient.cc @@ -61,8 +61,10 @@ Orient::Orient(int n, int nwant, int naccel, unsigned Oflg, unsigned Cflg, // Instantiate the pseudo-force helper // class if naccel>0 - if (naccel) accel = std::make_shared - (naccel, oflags & CENTER, oflags & AXIS); + if (naccel) { + accel = std::make_shared + (naccel, oflags & CENTER, oflags & AXIS); + } // Initialize last time to something // in the near infinite past @@ -549,12 +551,16 @@ void Orient::accumulate(double time, Component *c) } if ((cflags & DIAG) && myid==0) { - cout << " Orient info [" << time << ", " << c->name << "]: " - << used << " particles used, Ecurr=" << Ecurr - << " Center=" - << center1[0] << ", " - << center1[1] << ", " - << center1[2] << endl; + std::cout << " Orient info [" << time << ", " << c->name << "]: " + << used << " particles used, Ecurr=" << Ecurr + << " Center=" + << center1[0] << ", " + << center1[1] << ", " + << center1[2] + << " Axis=" + << axis1[0] << ", " + << axis1[1] << ", " + << axis1[2] << std::endl; } if (static_cast(sumsA.size()) > keep + 1) { @@ -684,7 +690,7 @@ void Orient::accumulate(double time, Component *c) // Add to pseudo-acceleration estimator // if (accel) { - accel->add(time, center, axis); + accel->add(time, center1, axis1); } // Increment initial center according From dd2b6cd88a7da97f4690ae22558a0f9ad52a86f9 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 26 Dec 2024 17:54:21 -0500 Subject: [PATCH 79/80] Fill the state vector before approximating any values --- include/PseudoAccel.H | 47 ++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/include/PseudoAccel.H b/include/PseudoAccel.H index f3a71240..c7f88585 100644 --- a/include/PseudoAccel.H +++ b/include/PseudoAccel.H @@ -58,32 +58,37 @@ public: } } - if (CENTER) { - accel << - 2.0*std::get<0>(QuadLS(t, x).coefs()), - 2.0*std::get<0>(QuadLS(t, y).coefs()), - 2.0*std::get<0>(QuadLS(t, z).coefs()); - } + // Compute the acceleration only if there are enough elements + // + if (queue.size()==Nsize) { - if (AXIS) { - auto [a, b, c] = QuadLS(t, u).coefs(); - auto [d, e, f] = QuadLS(t, v).coefs(); - auto [g, h, i] = QuadLS(t, w).coefs(); + if (CENTER) { + accel << + 2.0*std::get<0>(QuadLS(t, x).coefs()), + 2.0*std::get<0>(QuadLS(t, y).coefs()), + 2.0*std::get<0>(QuadLS(t, z).coefs()); + } - double T = t.back(); // Last eval time - - Eigen::Vector3d n, dndt, d2ndt2; - n << a*T*T+b*T+c, d*T*T+e*T+f, g*T*T+h*T+i; - dndt << 2.0*a*T+b, 2.0*d*T+e, 2.0*g*T+h; - d2ndt2 << 2.0*a, 2.0*d, 2.0*g; - - // Get angular acceleration and its derivative - omega = n.cross(dndt); - domdt = n.cross(d2ndt2); + if (AXIS) { + auto [a, b, c] = QuadLS(t, u).coefs(); + auto [d, e, f] = QuadLS(t, v).coefs(); + auto [g, h, i] = QuadLS(t, w).coefs(); + + double T = t.back(); // Last eval time + + Eigen::Vector3d n, dndt, d2ndt2; + n << a*T*T+b*T+c, d*T*T+e*T+f, g*T*T+h*T+i; + dndt << 2.0*a*T+b, 2.0*d*T+e, 2.0*g*T+h; + d2ndt2 << 2.0*a, 2.0*d, 2.0*g; + + // Get angular acceleration and its derivative + omega = n.cross(dndt); + domdt = n.cross(d2ndt2); + } } - } + // Return center acceleration, angular velocity and its derivative return {accel, omega, domdt}; } }; From 11012a62eb626ee1d16e83328f9d3b9d2c2850e6 Mon Sep 17 00:00:00 2001 From: "Martin D. Weinberg" Date: Thu, 26 Dec 2024 17:55:05 -0500 Subject: [PATCH 80/80] Add divide-by-zero check in quadratic regression formula --- include/QuadLS.H | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/include/QuadLS.H b/include/QuadLS.H index 94474551..1f5d4d36 100644 --- a/include/QuadLS.H +++ b/include/QuadLS.H @@ -36,9 +36,14 @@ private: double Sx2y = sumx2y - sumx2*sumy/n; double Sx2x2 = sumx4 - sumx2*sumx2/n; - _a = (Sx2y*Sxx - Sxy*Sxx2 ) / (Sxx*Sx2x2 - Sxx2*Sxx2); - _b = (Sxy*Sx2x2 - Sx2y*Sxx2) / (Sxx*Sx2x2 - Sxx2*Sxx2); - _c = (sumy - sumx2*_a - sumx*_b) / n; + double denom = Sxx*Sx2x2 - Sxx2*Sxx2; + + if (fabs(denom)>0.0) { + _a = (Sx2y*Sxx - Sxy*Sxx2 ) / denom; + _b = (Sxy*Sx2x2 - Sx2y*Sxx2) / denom; + _c = (sumy - sumx2*_a - sumx*_b) / n; + } else _a = _b = _c = 0.0; + } else { _a = _b = _c = 0.0;