LCOV - code coverage report
Current view: top level - ttsketch - TTHelper.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 132 211 62.6 %
Date: 2026-11-05 10:19:04 Functions: 8 13 61.5 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2026 Nils E. Strand, Siyao Yang, Yuehaw Khoo, Aaron R. Dinner
       3             : 
       4             :    See the COPYRIGHT file distributed with this software for license details.
       5             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
       6             : #include "TTHelper.h"
       7             : #include "tools/OpenMP.h"
       8             : #include <cstdio>
       9             : #include <stdexcept>
      10             : #ifdef __PLUMED_HAS_LIBITENSOR
      11             : #ifdef __PLUMED_HAS_LIBHDF5
      12             : 
      13             : using namespace itensor;
      14             : 
      15             : namespace PLMD {
      16             : namespace ttsketch {
      17             : 
      18          43 : void ttWrite(const std::string& filename, const MPS& tt, unsigned count) {
      19             :   // count starts at 2 on the first sketch call; open for writing on first call, append thereafter
      20          43 :   auto f = count == 2 ? h5_open(filename, 'w') : h5_open(filename, 'a');
      21         172 :   h5_write(f, "tt_" + std::to_string(count - 1), tt);
      22          43 : }
      23             : 
      24           8 : MPS ttRead(const std::string& filename, unsigned count) {
      25             :   auto f = h5_open(filename, 'r');
      26          32 :   return h5_read<MPS>(f, "tt_" + std::to_string(count - 1));
      27             : }
      28             : 
      29           0 : void ttSumWrite(const std::string& filename, const MPS& tt, unsigned count) {
      30             :   auto f = h5_open(filename, 'a');
      31           0 :   h5_write(f, "vb_" + std::to_string(count - 1), tt);
      32           0 : }
      33             : 
      34           0 : MPS ttSumRead(const std::string& filename, unsigned count) {
      35             :   auto f = h5_open(filename, 'r');
      36           0 :   return h5_read<MPS>(f, "vb_" + std::to_string(count - 1));
      37             : }
      38             : 
      39           5 : unsigned ttCount(const std::string& filename) {
      40             :   {
      41             :     // h5::file throws (and HDF5 prints an error stack) on a missing file
      42           5 :     FILE* fp = std::fopen(filename.c_str(), "r");
      43           5 :     if(!fp) {
      44             :       return 0;
      45             :     }
      46           4 :     std::fclose(fp);
      47             :   }
      48             :   auto f = h5_open(filename, 'r');
      49           4 :   h5::group g(f);
      50             :   unsigned n = 0;
      51          18 :   while(g.has_subgroup("tt_" + std::to_string(n + 1))) {
      52             :     ++n;
      53             :   }
      54             :   return n;
      55             : }
      56             : 
      57          13 : void ttMomentsWrite(const std::string& filename, const MPS& B, const std::vector<ITensor>& A, unsigned count) {
      58             :   auto f = h5_open(filename, 'a');
      59          13 :   const std::string tag = std::to_string(count - 1);
      60          39 :   h5_write(f, "bprev_" + tag, B);
      61          31 :   for(unsigned k = 0; k < A.size(); ++k) {
      62          90 :     h5_write(f, "aprev_" + tag + "_" + std::to_string(k + 1), A[k]);
      63             :   }
      64          13 : }
      65             : 
      66           3 : bool ttMomentsRead(const std::string& filename, unsigned count, unsigned nA, MPS& B, std::vector<ITensor>& A) {
      67             :   auto f = h5_open(filename, 'r');
      68           3 :   h5::group g(f);
      69           3 :   const std::string tag = std::to_string(count - 1);
      70           6 :   if(!g.has_subgroup("bprev_" + tag)) {
      71             :     return false;
      72             :   }
      73           5 :   for(unsigned k = 0; k < nA; ++k) {
      74           9 :     if(!g.has_subgroup("aprev_" + tag + "_" + std::to_string(k + 1))) {
      75             :       return false;
      76             :     }
      77             :   }
      78           6 :   B = h5_read<MPS>(f, "bprev_" + tag);
      79           2 :   A.resize(nA);
      80           5 :   for(unsigned k = 0; k < nA; ++k) {
      81          15 :     A[k] = h5_read<ITensor>(f, "aprev_" + tag + "_" + std::to_string(k + 1));
      82             :   }
      83             :   return true;
      84             : }
      85             : 
      86             : // Evaluate TT and gradient together using a prefix-suffix sweep: O(d) contractions.
      87             : //
      88             : // For a TT of length d:
      89             : //   L[i] = (G_1 * phi_1) * ... * (G_i * phi_i)   [left partial product up to i]
      90             : //   R[i] = (G_i * phi_i) * ... * (G_d * phi_d)   [right partial product from i]
      91             : //
      92             : // val       = elt(L[d])
      93             : // grad[k]   = L[k-1] * (G_k * dphi_k) * R[k+1]
      94             : std::pair<double, std::vector<double>>
      95          60 :                                     ttEvalAndGrad(const itensor::MPS& tt,
      96             :                                         const std::vector<BasisFunc>& basis,
      97             :                                         const std::vector<double>& elements,
      98             : bool conv) {
      99          60 :   int d = length(tt);
     100          60 :   auto s = siteInds(tt);
     101          60 :   const unsigned nt = OpenMP::getNumThreads();
     102             : 
     103             :   // Cache cores and build phi/dphi — all independent across dimensions
     104          60 :   std::vector<ITensor> cores(d), phi(d), dphi(d);
     105          60 :   #pragma omp parallel for num_threads(nt) schedule(static)
     106             :   for (int i = 0; i < d; ++i) {
     107             :     cores[i] = tt(i + 1);
     108             :     phi[i] = dphi[i] = ITensor(s(i + 1));
     109             :     for (int j = 1; j <= dim(s(i + 1)); ++j) {
     110             :       phi[i].set(s(i + 1) = j,  basis[i](elements[i], j, conv));
     111             :       dphi[i].set(s(i + 1) = j, basis[i].grad(elements[i], j, conv));
     112             :     }
     113             :   }
     114             : 
     115             :   // Prefix-suffix sweep
     116          60 :   std::vector<ITensor> L(d + 1), R(d + 1);
     117          60 :   L[0] = ITensor(1.0);
     118         240 :   for (int i = 0; i < d; ++i) {
     119         180 :     L[i + 1] = L[i] * (cores[i] * phi[i]);
     120             :   }
     121          60 :   R[d] = ITensor(1.0);
     122         240 :   for (int i = d - 1; i >= 0; --i) {
     123         180 :     R[i] = (cores[i] * phi[i]) * R[i + 1];
     124             :   }
     125             : 
     126          60 :   double val = elt(L[d]);
     127             : 
     128             :   // Gradient combination — independent across k
     129          60 :   std::vector<double> grad(d);
     130          60 :   #pragma omp parallel for num_threads(nt) schedule(static)
     131             :   for (int k = 0; k < d; ++k) {
     132             :     grad[k] = elt(L[k] * (cores[k] * dphi[k]) * R[k + 1]);
     133             :   }
     134             : 
     135          60 :   return {val, grad};
     136          60 : }
     137             : 
     138          96 : DenseTT::DenseTT(const MPS& tt) {
     139          96 :   const int d = length(tt);
     140          96 :   rank_.assign(d + 1, 1);
     141          96 :   nbasis_.resize(d);
     142          96 :   core_.resize(d);
     143         333 :   for(int i = 1; i <= d; ++i) {
     144         237 :     const Index s = siteIndex(tt, i);
     145         237 :     const Index l = i > 1 ? linkIndex(tt, i - 1) : Index();
     146         237 :     const Index r = i < d ? linkIndex(tt, i) : Index();
     147         237 :     const int rl = i > 1 ? dim(l) : 1;
     148         237 :     const int rr = i < d ? dim(r) : 1;
     149         237 :     const int n = dim(s);
     150         237 :     rank_[i] = rr;
     151         237 :     nbasis_[i - 1] = n;
     152             :     // the core has exactly the indices (l,s,r) without the missing boundary bonds
     153         333 :     if(order(tt(i)) != 1 + (i > 1) + (i < d)) {
     154           0 :       throw std::runtime_error("unexpected index structure in TT core " + std::to_string(i));
     155             :     }
     156             :     auto& c = core_[i - 1];
     157         237 :     c.resize(static_cast<size_t>(rl) * n * rr);
     158        2501 :     for(int a = 1; a <= rl; ++a) {
     159       18070 :       for(int j = 1; j <= n; ++j) {
     160      372716 :         for(int b = 1; b <= rr; ++b) {
     161             :           double v;
     162      356910 :           if(i > 1 && i < d) {
     163      681308 :             v = tt(i).elt(l = a, s = j, r = b);
     164       16256 :           } else if(i > 1) {
     165       16270 :             v = tt(i).elt(l = a, s = j);
     166        8121 :           } else if(i < d) {
     167       16242 :             v = tt(i).elt(s = j, r = b);
     168             :           } else {
     169           0 :             v = tt(i).elt(s = j);
     170             :           }
     171      356910 :           c[(static_cast<size_t>(a - 1) * n + (j - 1)) * rr + (b - 1)] = v;
     172             :         }
     173             :       }
     174             :     }
     175             :   }
     176          96 : }
     177             : 
     178         823 : double DenseTT::eval(const std::vector<BasisFunc>& basis, const std::vector<double>& elements, bool conv, std::vector<double>* grad) const {
     179         823 :   const unsigned d = core_.size();
     180             :   // contracted cores: M[i] = sum_j core_i[:,j,:] phi_j(x_i), D[i] the same with dphi
     181         823 :   thread_local std::vector<std::vector<double>> M, D;
     182         823 :   thread_local std::vector<double> phi, dphi;
     183         823 :   M.resize(d);
     184         823 :   D.resize(d);
     185        2786 :   for(unsigned i = 0; i < d; ++i) {
     186        1963 :     const int rl = rank_[i], rr = rank_[i + 1], n = nbasis_[i];
     187        1963 :     phi.resize(n);
     188        1963 :     dphi.resize(n);
     189        1963 :     basis[i].evalAll(elements[i], conv, phi.data(), dphi.data());
     190        1963 :     M[i].assign(static_cast<size_t>(rl) * rr, 0.0);
     191        1963 :     if(grad) {
     192         696 :       D[i].assign(static_cast<size_t>(rl) * rr, 0.0);
     193             :     }
     194             :     const double* c = core_[i].data();
     195       14839 :     for(int a = 0; a < rl; ++a) {
     196       12876 :       double* m = M[i].data() + static_cast<size_t>(a) * rr;
     197       12876 :       double* g = grad ? D[i].data() + static_cast<size_t>(a) * rr : nullptr;
     198      101122 :       for(int j = 0; j < n; ++j) {
     199       88246 :         const double* cj = c + (static_cast<size_t>(a) * n + j) * rr;
     200       88246 :         const double p = phi[j];
     201     1839305 :         for(int b = 0; b < rr; ++b) {
     202     1751059 :           m[b] += cj[b] * p;
     203             :         }
     204       88246 :         if(g) {
     205       29029 :           const double dp = dphi[j];
     206      569502 :           for(int b = 0; b < rr; ++b) {
     207      540473 :             g[b] += cj[b] * dp;
     208             :           }
     209             :         }
     210             :       }
     211             :     }
     212             :   }
     213             :   // left sweep: left[i] = M[0]...M[i-1] (row vector of size r_i)
     214         823 :   thread_local std::vector<std::vector<double>> left, right;
     215         823 :   left.resize(d + 1);
     216         823 :   left[0].assign(1, 1.0);
     217        2786 :   for(unsigned i = 0; i < d; ++i) {
     218        1963 :     const int rl = rank_[i], rr = rank_[i + 1];
     219        1963 :     left[i + 1].assign(rr, 0.0);
     220       14839 :     for(int a = 0; a < rl; ++a) {
     221       12876 :       const double la = left[i][a];
     222       12876 :       const double* m = M[i].data() + static_cast<size_t>(a) * rr;
     223      263359 :       for(int b = 0; b < rr; ++b) {
     224      250483 :         left[i + 1][b] += la * m[b];
     225             :       }
     226             :     }
     227             :   }
     228         823 :   const double val = left[d][0];
     229         823 :   if(grad) {
     230             :     // right sweep: right[i] = M[i]...M[d-1] (column vector of size r_i)
     231         287 :     right.resize(d + 1);
     232         287 :     right[d].assign(1, 1.0);
     233         983 :     for(int i = d - 1; i >= 0; --i) {
     234         696 :       const int rl = rank_[i], rr = rank_[i + 1];
     235         696 :       right[i].assign(rl, 0.0);
     236        4904 :       for(int a = 0; a < rl; ++a) {
     237        4208 :         const double* m = M[i].data() + static_cast<size_t>(a) * rr;
     238             :         double sum = 0.0;
     239       81486 :         for(int b = 0; b < rr; ++b) {
     240       77278 :           sum += m[b] * right[i + 1][b];
     241             :         }
     242        4208 :         right[i][a] = sum;
     243             :       }
     244             :     }
     245             :     // grad_i = left[i] * D[i] * right[i+1]
     246         287 :     grad->resize(d);
     247         983 :     for(unsigned i = 0; i < d; ++i) {
     248         696 :       const int rl = rank_[i], rr = rank_[i + 1];
     249             :       double sum = 0.0;
     250        4904 :       for(int a = 0; a < rl; ++a) {
     251        4208 :         const double* g = D[i].data() + static_cast<size_t>(a) * rr;
     252             :         double t = 0.0;
     253       81486 :         for(int b = 0; b < rr; ++b) {
     254       77278 :           t += g[b] * right[i + 1][b];
     255             :         }
     256        4208 :         sum += left[i][a] * t;
     257             :       }
     258         696 :       (*grad)[i] = sum;
     259             :     }
     260             :   }
     261         823 :   return val;
     262             : }
     263             : 
     264             : // ttEval delegates to ttEvalAndGrad and discards the gradient.
     265             : // Called infrequently (error measurement, well-tempered height) so the
     266             : // cost of computing dphi is negligible relative to avoiding code duplication.
     267           0 : double ttEval(const MPS& tt,
     268             :               const std::vector<BasisFunc>& basis,
     269             :               const std::vector<double>& elements,
     270             :               bool conv) {
     271           0 :   return ttEvalAndGrad(tt, basis, elements, conv).first;
     272             : }
     273             : 
     274             : // Compute covariance matrix, marginal means, and partition function of the TT distribution.
     275             : // Precomputes ITensors for the three moment integrals int0/int1/int2 per dimension,
     276             : // then evaluates expectations as TT contractions with these integral vectors.
     277           0 : std::tuple<Matrix<double>, std::vector<double>, double> covMat(const MPS& tt, const std::vector<BasisFunc>& basis) {
     278           0 :   int d = length(tt);
     279           0 :   auto s = siteInds(tt);
     280             :   // integral vectors: int0[i][j] = int phi_j(x_i) dx_i, etc.
     281           0 :   std::vector<ITensor> basis_int0(d), basis_int1(d), basis_int2(d);
     282           0 :   for(int i = 1; i <= d; ++i) {
     283           0 :     basis_int0[i - 1] = basis_int1[i - 1] = basis_int2[i - 1] = ITensor(s(i));
     284           0 :     for(int j = 1; j <= dim(s(i)); ++j) {
     285           0 :       basis_int0[i - 1].set(s(i) = j, basis[i - 1].int0(j));
     286           0 :       basis_int1[i - 1].set(s(i) = j, basis[i - 1].int1(j));
     287           0 :       basis_int2[i - 1].set(s(i) = j, basis[i - 1].int2(j));
     288             :     }
     289             :   }
     290             :   // Z = int tt(x) dx (partition function); normalize to get probability density rho
     291           0 :   auto Z = tt(1) * basis_int0[0];
     292           0 :   for(int i = 2; i <= d; ++i) {
     293           0 :     Z *= tt(i) * basis_int0[i - 1];
     294             :   }
     295           0 :   auto rho = tt;
     296           0 :   rho /= elt(Z);
     297             :   // ei[k]  = E[x_k],  eii[k] = E[x_k^2],  eij[k][l] = E[x_k * x_l] for k < l
     298           0 :   std::vector<double> ei(d), eii(d);
     299           0 :   Matrix<double> eij(d, d);
     300           0 :   for(int k = 1; k <= d; ++k) {
     301             :     // replace dimension k's integral vector with int1 (resp. int2) to get E[x_k] (resp. E[x_k^2])
     302           0 :     auto eival = rho(1) * (k == 1 ? basis_int1[0] : basis_int0[0]);
     303           0 :     auto eiival = rho(1) * (k == 1 ? basis_int2[0] : basis_int0[0]);
     304           0 :     for(int i = 2; i <= d; ++i) {
     305           0 :       eival *= rho(i) * (k == i ? basis_int1[i - 1] : basis_int0[i - 1]);
     306           0 :       eiival *= rho(i) * (k == i ? basis_int2[i - 1] : basis_int0[i - 1]);
     307             :     }
     308           0 :     ei[k - 1] = elt(eival);
     309           0 :     eii[k - 1] = elt(eiival);
     310           0 :     for(int l = k + 1; l <= d; ++l) {
     311             :       // replace both dimensions k and l with int1 to get E[x_k * x_l]
     312           0 :       auto eijval = rho(1) * (k == 1 ? basis_int1[0] : basis_int0[0]);
     313           0 :       for(int i = 2; i <= d; ++i) {
     314           0 :         eijval *= rho(i) * (k == i || l == i ? basis_int1[i - 1] : basis_int0[i - 1]);
     315             :       }
     316           0 :       eij(k - 1, l - 1) = elt(eijval);
     317           0 :     }
     318           0 :   }
     319             :   // sigma(k,l) = Cov(x_k, x_l) = E[x_k*x_l] - E[x_k]*E[x_l]
     320           0 :   Matrix<double> sigma(d, d);
     321           0 :   for(int k = 1; k <= d; ++k) {
     322           0 :     for(int l = k; l <= d; ++l) {
     323           0 :       sigma(k - 1, l - 1) = sigma(l - 1, k - 1) = k == l ? eii[k - 1] - pow(ei[k - 1], 2) : eij(k - 1, l - 1) - ei[k - 1] * ei[l - 1];
     324             :     }
     325             :   }
     326           0 :   return std::make_tuple(sigma, ei, elt(Z));
     327           0 : }
     328             : 
     329             : // Compute the 2D marginal density of the normalized TT distribution for dimensions
     330             : // pos1 and pos2 on a (bins x bins) grid. All other dimensions are integrated out
     331             : // analytically using int0, which collapses those TT cores to scalar factors.
     332           0 : void marginal2d(const MPS& tt, const std::vector<BasisFunc>& basis, int pos1, int pos2, Matrix<double>& grid, bool conv) {
     333           0 :   int bins = grid.nrows();
     334           0 :   int d = length(tt);
     335           0 :   auto s = siteInds(tt);
     336           0 :   std::vector<ITensor> basis_int0(d);
     337           0 :   for(int i = 1; i <= d; ++i) {
     338           0 :     basis_int0[i - 1] = ITensor(s(i));
     339           0 :     for(int j = 1; j <= dim(s(i)); ++j) {
     340           0 :       basis_int0[i - 1].set(s(i) = j, basis[i - 1].int0(j));
     341             :     }
     342             :   }
     343             :   // normalize to probability density
     344           0 :   auto Z = tt(1) * basis_int0[0];
     345           0 :   for(int i = 2; i <= d; ++i) {
     346           0 :     Z *= tt(i) * basis_int0[i - 1];
     347             :   }
     348           0 :   auto rho = tt;
     349           0 :   rho /= elt(Z);
     350           0 :   for(int i = 0; i < bins; ++i) {
     351           0 :     for(int j = 0; j < bins; ++j) {
     352           0 :       double x = basis[pos1 - 1].dom().first + i * (basis[pos1 - 1].dom().second - basis[pos1 - 1].dom().first) / bins;
     353           0 :       double y = basis[pos2 - 1].dom().first + j * (basis[pos2 - 1].dom().second - basis[pos2 - 1].dom().first) / bins;
     354           0 :       ITensor xevals(s(pos1)), yevals(s(pos2));
     355           0 :       for(int k = 1; k <= dim(s(pos1)); ++k) {
     356           0 :         xevals.set(s(pos1) = k, basis[pos1 - 1](x, k, conv));
     357           0 :         yevals.set(s(pos2) = k, basis[pos2 - 1](y, k, conv));
     358             :       }
     359           0 :       auto val = rho(1) * (pos1 == 1 ? xevals : basis_int0[0]);
     360           0 :       for(int k = 2; k <= d; ++k) {
     361           0 :         if(pos1 == k) {
     362           0 :           val *= rho(k) * xevals;
     363           0 :         } else if(pos2 == k) {
     364           0 :           val *= rho(k) * yevals;
     365             :         } else {
     366           0 :           val *= rho(k) * basis_int0[k - 1];
     367             :         }
     368             :       }
     369           0 :       grid(i, j) = elt(val);
     370           0 :     }
     371             :   }
     372           0 : }
     373             : 
     374             : }
     375             : }
     376             : #endif // __PLUMED_HAS_LIBHDF5
     377             : #endif // __PLUMED_HAS_LIBITENSOR

Generated by: LCOV version 1.16