diff --git a/manual/backflow_implementation.tex b/manual/backflow_implementation.tex index 62aa80a4ad..23ce179091 100644 --- a/manual/backflow_implementation.tex +++ b/manual/backflow_implementation.tex @@ -1,5 +1,3 @@ -\newcommand{\bs}{\boldsymbol} -\newcommand{\tr}{\text{tr}} \section{Slater-Backflow Wavefunction Implementation Details} For simplicity, consider $N$ identical fermions of the same spin (e.g. up electrons) at spatial locations $\{\bs{r}_1,\bs{r}_2,\dots,\bs{r}_{N}\}$. Then the Slater determinant can be written as diff --git a/manual/developing.tex b/manual/developing.tex index 0af4778e2d..a792aa18d8 100644 --- a/manual/developing.tex +++ b/manual/developing.tex @@ -6,4 +6,5 @@ \chapter{Development Guide} \input{coding_standards} \input{estimator_implementation} +\input{estimator_manager} \input{backflow_implementation} diff --git a/manual/estimator_manager.tex b/manual/estimator_manager.tex new file mode 100644 index 0000000000..e947a45903 --- /dev/null +++ b/manual/estimator_manager.tex @@ -0,0 +1,298 @@ +\section{Estimator Output} +\subsection{Estimator Definition} +For simplicity, consider a local property $O(\bs{R})$, where $\bs{R}$ is the collection of all particle coordinates. An \textit{estimator} for $O(\bs{R}) $ is a weighted average over walkers +\begin{align} +E[O] = \left(\sum\limits_{i=1}^{N^{tot}_{walker}} w_i O(\bs{R}_i) \right) / \left( \sum \limits_{i=1}^{N^{tot}_{walker}} w_i \right). \label{eq:estimator} +\end{align} +$N^{tot}_{walker}$ is the total number of walkers collected in the entire simulation. Notice, $N^{tot}_{walker}$ is typically far larger than the number of walkers held in memory at any given simulation step. $w_i$ is the weight of walker $i$. + +In a VMC simulation, the weight of every walkers is 1.0. Further, the number of walkers is constant at each step. Therefore, eq.~(\ref{eq:estimator}) simplifies to +\begin{align} +E_{VMC}[O] = \frac{1}{N_{step}N_{walker}^{ensemble}} \sum_{s,e} O(\bs{R}_{s,e}). +\end{align} +Each walker $\bs{R}_{s,e}$ is labeled by \textit{step index} s, and \textit{ensemble index} e. + +In a DMC simulation, the weight of each walker is different and may change from step to step. Further, the ensemble size varies from step to step. Therefore, eq.~(\ref{eq:estimator}) simplifies to +\begin{align} +E_{DMC}[O] = \frac{1}{N_{step}} \sum_{s} \left\{ \left(\sum_e w_{s,e} O(\bs{R}_{s,e}) \right) / \left( \sum \limits_{e} w_{s,e} \right) \right\}. +\end{align} + +I will refer to the average in the $\{\}$ as \textit{ensemble average} and the remaining averages \textit{block average}. The process of calculating $O(\bs{R})$ is \textit{evaluate}. + +\subsection{Class Relations} +A large number of classes are involved in the estimator collection process. They often have misleading class name or method name. Document gotchas in the following list: +\begin{enumerate} +\item \verb|EstimatorManager| is an unused copy of \verb|EstimatorManagerBase|. \verb|EstimatorManagerBase| is the class used in the QMC drivers. (PR \#371 explains this) +\item \verb|EstimatorManagerBase::Estimators| is completely different from \verb|QMCDriver::Estimators|, which is subtly different from \verb|QMCHamiltonianBase::Estimators|. The first is a list of pointers to \verb|ScalarEstimatorBase|. The second is the master estimator (one per MPI group). The third is the slave estimator that exists one per OpenMP thread. +\item \verb|QMCHamiltonian| is NOT a parent class of \verb|QMCHamiltonianBase|. Instead, \verb|QMCHamiltonian| owns two lists of \verb|QMCHamiltonianBase| named \verb|H| and \verb|auxH|. +\item \verb|QMCDriver::H| is NOT the same as \verb|QMCHamiltonian::H|. The first is a pointer to a \verb|QMCHamiltonian|. \verb|QMCHamiltonian::H| is a list. +\item \verb|EstimatorManager::stopBlock(std::vector)| is completely different from \verb|EstimatorManager::| +\verb|stopBlock(RealType)|, which is the same as \verb|stopBlock(RealType, true)|, but is subtly different from \verb|stopBlock(RealType, false)|. The first three methods are intended to be called by the master estimator which exists one per MPI group. The last method is intended to be called by the slave estimator which exists one per OpenMP thread. +\end{enumerate} + +\subsection{Estimator Output Stages} +%In QMCPACK, evaluation is done by \verb|QMCHamiltonianBase|; ensemble average is done either by a ``CloneDriver'' (e.g. \verb|VMCSingleOMP|, \verb|DMCOMP|) or \verb|ScalarEstimatorBase|; block average is done by \verb|ScalarEstimatorBase| or \verb|EstimatorManagerBase|. Walkers can be accessed by ``CloneDriver'' and \verb|QMCHamiltonianBase| but not by \verb|EstimatorManagerBase| or \verb|ScalarEstimatorBase|. Output files can be accessed by the latter two classes but not the former two. Therefore, in order to output estimators to file, data must be transferred from \textit{evaluate} classes to \textit{average} classes. + +Estimators take four conceptual stages to propagate to the output files: evaluate, load ensemble, unload ensemble, and collect. They are easier to understand in reverse order. + +\subsubsection{Collect Stage} +File output is performed by the master \verb|EstimatorManager| owned by \verb|QMCDriver|. The first 8+ entries in \verb|EstimatorManagerBase::AverageCache| will be written to scalar.dat. The remaining entries in \verb|AverageCache| will be written to stat.h5. File writing is triggered by \verb|EstimatorManagerBase|\\ \verb|::collectBlockAverages| inside \verb|EstimatorManagerBase::stopBlock|. + +\begin{lstlisting} +// In EstimatorManagerBase.cpp::collectBlockAverages + if(Archive) + { + *Archive << std::setw(10) << RecordCount; + int maxobjs=std::min(BlockAverages.size(),max4ascii); + for(int j=0; jwrite(AverageCache.data(),SquaredAverageCache.data()); + H5Fflush(h_file,H5F_SCOPE_LOCAL); + } +\end{lstlisting} + +\verb|EstimatorManagerBase::collectBlockAverages| is triggered from master-thread estimator via either \verb|stopBlock(std::vector)| or \verb|stopBlock(RealType, true)|. Notice, file writing is NOT triggered by the slave-thread estimator method \verb|stopBlock(RealType, false)|. + +\begin{lstlisting} +// In EstimatorManagerBase.cpp +void EstimatorManagerBase::stopBlock(RealType accept, bool collectall) +{ + //take block averages and update properties per block + PropertyCache[weightInd]=BlockWeight; + PropertyCache[cpuInd] = MyTimer.elapsed(); + PropertyCache[acceptInd] = accept; + for(int i=0; itakeBlockAverage(AverageCache.begin(),SquaredAverageCache.begin()); + if(Collectables) + { + Collectables->takeBlockAverage(AverageCache.begin(),SquaredAverageCache.begin()); + } + if(collectall) + collectBlockAverages(1); +} +\end{lstlisting} + +\begin{lstlisting} +// In ScalarEstimatorBase.h +template +inline void takeBlockAverage(IT first, IT first_sq) +{ + first += FirstIndex; + first_sq += FirstIndex; + for(int i=0; iaccumulate(W,W.begin(),W.end(),norm); + if(Collectables)//collectables are normalized by QMC drivers + Collectables->accumulate_all(W.Collectables,1.0); +} +\end{lstlisting} + +\begin{lstlisting} +void EstimatorManagerBase::accumulate(MCWalkerConfiguration& W + , MCWalkerConfiguration::iterator it + , MCWalkerConfiguration::iterator it_end) +{ // intended to be called slaveEstimator only + BlockWeight += it_end-it; + RealType norm=1.0/W.getGlobalNumWalkers(); + for(int i=0; i< Estimators.size(); i++) + Estimators[i]->accumulate(W,it,it_end,norm); + if(Collectables) + Collectables->accumulate_all(W.Collectables,1.0); +} +\end{lstlisting} + +\begin{lstlisting} +// In LocalEnergyEstimator.h +inline void accumulate(const Walker_t& awalker, RealType wgt) +{ // ensemble average W.Properties + // expect ePtr to be W.Properties; expect wgt = 1/GlobalNumberOfWalkers + const RealType* restrict ePtr = awalker.getPropertyBase(); + RealType wwght= wgt* awalker.Weight; + scalars[0](ePtr[LOCALENERGY],wwght); + scalars[1](ePtr[LOCALENERGY]*ePtr[LOCALENERGY],wwght); + scalars[2](ePtr[LOCALPOTENTIAL],wwght); + for(int target=3, source=FirstHamiltonian; target + inline + void saveProperty(IT first) + { // expect first to be W.Properties + first[LOCALPOTENTIAL]= LocalEnergy-KineticEnergy; + copy(Observables.begin(),Observables.end(),first+myIndex); + } +\end{lstlisting} + +\verb|Collectables|'s load stage is combined with its evaluate stage. + +\subsubsection{Evaluate Stage} + +The master \verb|QMCHamiltonian::Observables| is populated by slave \verb|QMCHamiltonianBase| +\verb|::setObservables|. However, the call signature must be \verb|QMCHamiltonianBase::setObservables(| +\verb|QMCHamiltonian::Observables)|. This call signature is enforced by \verb|QMCHamiltonian::evaluate| and \verb|QMCHamiltonian::auxHevaluate|. + +\begin{lstlisting} +// In QMCHamiltonian.cpp +QMCHamiltonian::Return_t +QMCHamiltonian::evaluate(ParticleSet& P) +{ + LocalEnergy = 0.0; + for(int i=0; istart(); + LocalEnergy += H[i]->evaluate(P); + H[i]->setObservables(Observables); +#if !defined(REMOVE_TRACEMANAGER) + H[i]->collect_scalar_traces(); +#endif + myTimers[i]->stop(); + H[i]->setParticlePropertyList(P.PropertyList,myIndex); + } + KineticEnergy=H[0]->Value; + P.PropertyList[LOCALENERGY]=LocalEnergy; + P.PropertyList[LOCALPOTENTIAL]=LocalEnergy-KineticEnergy; + // auxHevaluate(P); + return LocalEnergy; +} +\end{lstlisting} + +\begin{lstlisting} +// In QMCHamiltonian.cpp +void QMCHamiltonian::auxHevaluate(ParticleSet& P, Walker_t& ThisWalker) +{ +#if !defined(REMOVE_TRACEMANAGER) + collect_walker_traces(ThisWalker,P.current_step); +#endif + for(int i=0; isetHistories(ThisWalker); + RealType sink = auxH[i]->evaluate(P); + auxH[i]->setObservables(Observables); +#if !defined(REMOVE_TRACEMANAGER) + auxH[i]->collect_scalar_traces(); +#endif + auxH[i]->setParticlePropertyList(P.PropertyList,myIndex); + } +} +\end{lstlisting} + +\subsection{Estimator Use Cases} + +\subsubsection{VMCSingleOMP pseudo code} +\begin{lstlisting} +bool VMCSingleOMP::run() +{ + masterEstimator->start(nBlocks); + for (int ip=0; ipstartRun(nBlocks,false); // slaveEstimator->start(blocks, record) + + do // block + { + #pragma omp parallel + { + Movers[ip]->startBlock(nSteps); // slaveEstimator->startBlock(steps) + RealType cnorm = 1.0/static_cast(wPerNode[ip+1]-wPerNode[ip]); + do // step + { + wClones[ip]->resetCollectables(); + Movers[ip]->advanceWalkers(wit, wit_end, recompute); + wClones[ip]->Collectables *= cnorm; + Movers[ip]->accumulate(wit, wit_end); + } // end step + Movers[ip]->stopBlock(false); // slaveEstimator->stopBlock(acc, false) + } // end omp + masterEstimator->stopBlock(estimatorClones); // write files + } // end block + masterEstimator->stop(estimatorClones); +} +\end{lstlisting} + +\subsubsection{DMCOMP pseudo code} +\begin{lstlisting} +bool DMCOMP::run() +{ + masterEstimator->setCollectionMode(true); + + masterEstimator->start(nBlocks); + for(int ip=0; ipstartRun(nBlocks,false); // slaveEstimator->start(blocks, record) + + do // block + { + masterEstimator->startBlock(nSteps); + for(int ip=0; ipstartBlock(nSteps); // slaveEstimator->startBlock(steps) + + do // step + { + #pragma omp parallel + { + wClones[ip]->resetCollectables(); + // advanceWalkers + } // end omp + + //branchEngine->branch + { // In WalkerControlMPI.cpp::branch + wgt_inv=WalkerController->NumContexts/WalkerController->EnsembleProperty.Weight; + walkers.Collectables *= wgt_inv; + slaveEstimator->accumulate(walkers); + } + masterEstimator->stopBlock(acc) // write files + } // end for step + } // end for block + + masterEstimator->stop(); +} +\end{lstlisting} + +\subsection{Summary} + +Two ensemble-level data structures \verb|ParticleSet::Properties| and \verb|::Collectables| serve as intermediaries between evaluate classes and output classes to scalar.dat and stat.h5. \verb|Properties| appears in both scalar.dat and stat.h5, whereas \verb|Collectables| appears only in stat.h5. \verb|Properties| is overwritten by \verb|QMCHamiltonian::Observables| at the end of each step. \verb|QMCHamiltonian::Observables| is filled upon call to \verb|QMCHamiltonian::evaluate| and \verb|::auxHevaluate|. \verb|Collectables| is zeroed at the beginning of each step and accumulated upon call to \verb|::auxHevaluate|. + +Data are outputted to scalar.dat in 4 stages: evaluate, load, unload, and collect. In the evaluate stage, \verb|QMCHamiltonian::Observables| is populated by a list of \verb|QMCHamiltonianBase|. In the load stage, \verb|QMCHamiltonian::Observables| is transfered to \verb|Properties| by \verb|QMCDriver|. In the unload stage, \verb|Properties| is copied to \verb|LocalEnergyEstimator::scalars|. In the collect stage, \verb|LocalEnergyEstimator::scalars| is block-averaged to \verb|EstimatorManagerBase|\\ \verb|::AverageCache| and dumped to file. For \verb|Collectables|, the evaluate and load stages are combined in a call to \verb|QMCHamiltonian::auxHevaluate|. In the unload stage, \verb|Collectables| is copied to \verb|CollectablesEstimator::scalars|. In the collect stage, \verb|CollectablesEstimator|\\ \verb|::scalars| is block-averaged to \verb|EstimatorManagerBase::AverageCache| and dumped to file. + +\subsection{Appendix: dmc.dat} + +There is an additional data structure \verb|ParticleSet::EnsembleProperty|, which is managed by \verb|WalkerControlBase::EnsembleProperty| and directly dumped to dmc.dat via its own averaging procedure. dmc.dat is written by \verb|WalkerControlBase::measureProperties|, which is called by \verb|WalkerControlBase::branch|, which is called by \verb|SimpleFixedNodeBranch|\\ \verb|::branch| for example. diff --git a/manual/qmcpack_manual.tex b/manual/qmcpack_manual.tex index d8a989804b..90714b1399 100644 --- a/manual/qmcpack_manual.tex +++ b/manual/qmcpack_manual.tex @@ -164,6 +164,8 @@ \newcommand{\overlap}[2]{\langle #1 \lvert #2 \rangle} \newcommand{\operator}[3]{\ket{#1} #2 \bra{#3}} \newcommand{\idop}{\hat{\mathbb{1}}} +\newcommand{\bs}{\boldsymbol} +\newcommand{\tr}{\text{tr}} % trace \begin{document} diff --git a/tests/estimator/CMakeLists.txt b/tests/estimator/CMakeLists.txt index 96d517556b..1ad39d650b 100644 --- a/tests/estimator/CMakeLists.txt +++ b/tests/estimator/CMakeLists.txt @@ -46,3 +46,33 @@ if (add_test) latdev_check.py ) endif() + +set(sofk_python_reqs numpy;pandas;h5py) +CHECK_PYTHON_REQS(sofk_python_reqs estimator-sofk add_test) + +if (add_test) + SIMPLE_RUN_AND_CHECK(estimator-sofk_pbyp-properties + "${CMAKE_SOURCE_DIR}/tests/estimator/sofk" + pbyp_dat-h5.xml + 1 16 + check_properties_h5dat.py dat-h5_pbyp + ) + SIMPLE_RUN_AND_CHECK(estimator-sofk_pbyp-collectables + "${CMAKE_SOURCE_DIR}/tests/estimator/sofk" + pbyp_dat-h5.xml + 1 16 + check_collectables_h5dat.py dat-h5_pbyp + ) + SIMPLE_RUN_AND_CHECK(estimator-sofk_allp-properties + "${CMAKE_SOURCE_DIR}/tests/estimator/sofk" + allp_dat-h5.xml + 1 16 + check_properties_h5dat.py dat-h5_allp + ) + SIMPLE_RUN_AND_CHECK(estimator-sofk_allp-collectables + "${CMAKE_SOURCE_DIR}/tests/estimator/sofk" + allp_dat-h5.xml + 1 16 + check_collectables_h5dat.py dat-h5_allp + ) +endif() diff --git a/tests/estimator/sofk/allp_dat-h5.xml b/tests/estimator/sofk/allp_dat-h5.xml new file mode 100644 index 0000000000..c531707ebd --- /dev/null +++ b/tests/estimator/sofk/allp_dat-h5.xml @@ -0,0 +1,91 @@ + + + + + + + + + 3.77945227 0.00000000 0.00000000 + -0.00000000 3.77945227 0.00000000 + -0.00000000 -0.00000000 3.77945227 + + + p p p + + 15 + + + + -1 + 1.0 + + + -1 + 1.0 + + + + + 1 + 1 + 1 + 1837.36221934 + + 0.00000000 0.00000000 0.00000000 + 1.88972614 1.88972614 1.88972614 + + + + + + + + + + + + + + + + + +0.00206602038 -0.002841926986 0.0036266191 -0.001913930279 8.457152991e-06 +0.0007380321824 3.635172529e-05 0.0001299635851 + + + + + + +0.5954603818 0.5062051797 0.3746940461 0.2521010502 0.1440163317 0.07796688253 +0.03804420551 0.01449320872 + + + + + + + + + + + + + + 64 + 16 + 2 + 2 + 2.0 + 16 + + + 64 + 16 + 1 + 2.0 + 0 + + diff --git a/tests/estimator/sofk/check_collectables_h5dat.py b/tests/estimator/sofk/check_collectables_h5dat.py new file mode 100755 index 0000000000..5b358439eb --- /dev/null +++ b/tests/estimator/sofk/check_collectables_h5dat.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +import sys +import os +import h5py +import numpy as np +from check_properties_h5dat import read + + +def get_last_sk(fdat,fh5): + """ extract S(k) at the longest k vector from scalar.dat and stat.h5 + + Args: + fdat (str): name of scalar.dat file + fh5 (str): name of stat.h5 file + Return: + tuple: (myy, h5y), S(k_max) at each block from scalar.dat and stat.h5 + """ + + # get S(k) from scalar.dat + df = read(fdat) + sk_cols = [col for col in df.columns if col.startswith('sk')] + myy = df[sk_cols[-1]].values + + # get S(k) from stat.h5 + fp = h5py.File(fh5, 'r') + h5y = fp['h5sk/value'].value.T[-1] + fp.close() + + return myy, h5y +# end def + + +def show_scalar_trace(data, seriesl): + import matplotlib.pyplot as plt + method_map = {0:'VMC',1:'DMC'} + fig,ax_arr = plt.subplots(1, 2, sharey=True) + ax_arr[0].set_ylabel('S(k->inf)') + iplot = 0 + for iseries in seriesl: + ax = ax_arr[iplot] + ax.set_title(method_map[iseries]) + ax.set_xlabel('block') + ax.set_ylim(0.3, 1.2) + + entry = data[iseries] + daty = entry['daty'] + h5y = entry['h5y'] + + sline = ax.plot(daty) + hline = ax.plot(h5y, ls='--', lw=2, alpha=0.8) + + ax.legend( + handles = [sline[0], hline[0]] + ,labels = ['scalar.dat', 'stat.h5'] + ,loc=0 + ) + + iplot += 1 + # end for iseries + plt.show() + + +if __name__ == '__main__': + + prefix = sys.argv[1] + seriesl= [0,1] # a list of series IDs to check + + # check Properties v.s. Collectables + collectable_success_map = {} + data = {} + for iseries in seriesl: + + # define files to read + fdat = '%s.s00%d.scalar.dat' % (prefix, iseries) + fh5 = '%s.s00%d.stat.h5' % (prefix, iseries) + + daty, h5y = get_last_sk(fdat, fh5) + success = np.allclose(daty, h5y, atol=0.1) + collectable_success_map[iseries] = success + + # save data for plotting + data[iseries] = {'daty':daty, 'h5y':h5y} + # end for + all_success = np.all( collectable_success_map.values() ) + + if all_success: + sys.exit(0) + else: + #show_scalar_trace(data, seriesl) + sys.exit(1) + +# end __main__ diff --git a/tests/estimator/sofk/check_properties_h5dat.py b/tests/estimator/sofk/check_properties_h5dat.py new file mode 100755 index 0000000000..3e5c801353 --- /dev/null +++ b/tests/estimator/sofk/check_properties_h5dat.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +import os +import sys +import h5py +import numpy as np +import pandas as pd + + +def read(fdat): + """ read the scalar.dat file in table format readable by numpy.loadtxt. + + The header line should start with '#' and contain column labels. + + Args: + dat_fname (str): name of input file + Return: + pd.DataFrame: df containing the table of data + """ + with open(fdat, 'r') as fp: + header = fp.readline() + # end with + cols = header.replace('#', '').split() + df = pd.read_table(fdat, sep='\s+', comment='#', header=None, names=cols) + return df +# end def read + + +def compare_columns_dat_h5(fdat, fh5): + """ compare mutual data columns in scalar.dat and stat.h5 files + + Args: + fdat (str): name of scalar.dat file + fh5 (str): name of stat.h5 file + Return: + dict: a dictionary holding mutual columns names as key + """ + + # open database + df = read(fdat) + dat_cols = df.columns + + fp = h5py.File(fh5,'r') + h5_cols = fp.keys() + + # compare mutual columns in .dat v.s. .h5 + agree_map = {} # keep track of which columns agree + for col in h5_cols: + if col not in dat_cols: + continue + + # check if col agree between .dat and .h5 + + # get .h5 values + h5_loc = os.path.join(col, 'value') + h5y = fp[h5_loc].value[:,-1] + + # get .dat values + daty = df.loc[:,col].values + agree_map[col] = np.allclose(h5y,daty) + # end for col + + # close database + fp.close() + + if len(agree_map) == 0: + raise RuntimeError('%s and %s have no mutual column' % (fdat, fh5)) + + return agree_map +# end def + + +if __name__ == '__main__': + + prefix = sys.argv[1] + seriesl= [0,1] + + # check Properties + series_success_map = {} + for iseries in seriesl: + + # define files to read + fdat = '%s.s00%d.scalar.dat' % (prefix, iseries) + fh5 = '%s.s00%d.stat.h5' % (prefix, iseries) + + agree_map = compare_columns_dat_h5(fdat, fh5) + success = np.all( agree_map.values() ) + series_success_map[iseries] = success + # end for iseries + + all_success = np.all( series_success_map.values() ) + if all_success: + sys.exit(0) + else: + sys.exit(1) + +# end __main__ diff --git a/tests/estimator/sofk/pbyp_dat-h5.xml b/tests/estimator/sofk/pbyp_dat-h5.xml new file mode 100644 index 0000000000..4384ffd0c6 --- /dev/null +++ b/tests/estimator/sofk/pbyp_dat-h5.xml @@ -0,0 +1,92 @@ + + + + + + + + + 3.77945227 0.00000000 0.00000000 + -0.00000000 3.77945227 0.00000000 + -0.00000000 -0.00000000 3.77945227 + + + p p p + + 15 + + + + -1 + 1.0 + + + -1 + 1.0 + + + + + 1 + 1 + 1 + 1837.36221934 + + 0.00000000 0.00000000 0.00000000 + 1.88972614 1.88972614 1.88972614 + + + + + + + + + + + + + + + + + +0.00206602038 -0.002841926986 0.0036266191 -0.001913930279 8.457152991e-06 +0.0007380321824 3.635172529e-05 0.0001299635851 + + + + + + +0.5954603818 0.5062051797 0.3746940461 0.2521010502 0.1440163317 0.07796688253 +0.03804420551 0.01449320872 + + + + + + + + + + + + + + 64 + 16 + 2 + 2 + 2.0 + 16 + + + + 64 + 16 + 1 + 2.0 + 0 + + diff --git a/tests/estimator/sofk/pwscf.pwscf.h5 b/tests/estimator/sofk/pwscf.pwscf.h5 new file mode 120000 index 0000000000..7140f8e389 --- /dev/null +++ b/tests/estimator/sofk/pwscf.pwscf.h5 @@ -0,0 +1 @@ +../../solids/bccH_1x1x1_ae/pwscf.pwscf.h5 \ No newline at end of file