QMCPACK
test_MagnetizationDensity.cpp
Go to the documentation of this file.
1 //////////////////////////////////////////////////////////////////////////////////////
2 // This file is distributed under the University of Illinois/NCSA Open Source License.
3 // See LICENSE file in top directory for details.
4 //
5 // Copyright (c) 2022 QMCPACK developers
6 //
7 // File developed by: Raymond Clay, rclay@sandia.gov, Sandia National Laboratories
8 // Cody A. Melton, cmelton@sandia.gov, Sandia National Laboratories
9 //
10 // File created by: Raymond Clay, rclay@sandia.gov, Sandia National Laboratories
11 //////////////////////////////////////////////////////////////////////////////////////
12 
13 
14 #include "catch.hpp"
17 #include "Configuration.h"
18 //#include "QMCHamiltonians/MagDensityEstimator.h"
19 //for wavefunction
20 #include "OhmmsData/Libxml2Doc.h"
26 #include "Particle/Walker.h"
28 //for nonlocal moves
30 //for Hamiltonian manipulations.
31 #include "Particle/ParticleSet.h"
34 
36 #include "EstimatorTesting.h"
37 
39 
40 using std::string;
41 
42 
43 namespace qmcplusplus
44 {
45 namespace testing
46 {
48 {
52  using Real = WF::Real;
53  using Value = WF::Value;
54 
55 public:
57  {
58  MagnetizationDensity magdens2(magdens);
59  CHECK(magdens.nsamples_ == magdens2.nsamples_);
60  CHECK(magdens.integrator_ == magdens2.integrator_);
61  CHECK(magdens.data_locality_ == magdens2.data_locality_);
62  for (int idim = 0; idim < OHMMS_DIM; idim++)
63  {
64  CHECK(magdens.gdims_[idim] == magdens2.gdims_[idim]);
65  CHECK(magdens.grid_[idim] == magdens2.grid_[idim]);
66  CHECK(magdens.rcorner_[idim] == Approx(magdens2.rcorner_[idim]));
67  CHECK(magdens.center_[idim] == Approx(magdens2.center_[idim]));
68  }
69  }
70 
71  void testData(const MagnetizationDensity& magdens, const Data& data)
72  {
73  for (size_t i = 0; i < data.size(); i++)
74  CHECK(magdens.data_[i] == Approx(data[i]));
75  }
76 
78  {
79  //For xgrid, we use dx=1.0 from 0 to 8.
80  //For ygrid, we use f(x)=x^3.
81  std::vector<Value> ygrid = {0, 1, 8, 27, 64, 125, 216, 343, 512};
82  Value result_simpsons(0.0);
83  result_simpsons = magdens.integrateBySimpsonsRule(ygrid, Real(1.0));
84 
85  CHECK(std::real(result_simpsons) == Approx(Real(1024)));
86  }
87 
88  void testGrids(const MagnetizationDensity& magdens)
89  {
90  int npoints = 5;
91  std::vector<Real> xgrid(npoints);
92  Real start = 0.0;
93  Real stop = 1.5;
94 
95  Real delta = (stop - start) / Real(npoints - 1);
96 
97  magdens.generateUniformGrid(xgrid, start, stop);
98  for (int i = 0; i < npoints; i++)
99  CHECK(xgrid[i] == Approx(start + i * delta));
100 
101  FakeRandom rng;
102  magdens.generateRandomGrid(xgrid, rng, start, stop);
103 
104  for (int i = 0; i < npoints; i++)
105  {
106  bool ok = (xgrid[i] >= start) && (xgrid[i] <= stop);
107  CHECK(ok);
108  }
109  }
110  int computeBinAccessor(const MagnetizationDensity& magdens, const Position& r, const int spin_index)
111  {
112  return magdens.computeBin(r, spin_index);
113  }
114 };
115 } //namespace testing
116 
117 
118 #ifdef QMC_COMPLEX
119 TEST_CASE("MagnetizationDensity::MagnetizationDensity(SPInput, Lattice, SpeciesSet)", "[estimators]")
120 {
121  using namespace testing;
124  bool okay = doc.parseFromString(input_xml);
125  REQUIRE(okay);
126  xmlNodePtr node = doc.getRoot();
127  MagnetizationDensityInput mdi(node);
128 
130 
131  MagnetizationDensity magdens(std::move(mdi), lattice);
132  MagnetizationDensityTests magdenstest;
133  magdenstest.testCopyConstructor(magdens);
134 }
135 
136 TEST_CASE("MagnetizationDensity::spawnCrowdClone()", "[estimators]")
137 {
138  using namespace testing;
141  bool okay = doc.parseFromString(input_xml);
142  REQUIRE(okay);
143  xmlNodePtr node = doc.getRoot();
144  MagnetizationDensityInput mdi(node);
145 
147 
148  MagnetizationDensity original(std::move(mdi), lattice);
149  auto clone = original.spawnCrowdClone();
150  REQUIRE(clone != nullptr);
151  REQUIRE(clone.get() != &original);
152  REQUIRE(dynamic_cast<decltype(&original)>(clone.get()) != nullptr);
153 }
154 TEST_CASE("MagnetizationDensity::integrals", "[estimators]")
155 {
156  using namespace testing;
159  bool okay = doc.parseFromString(input_xml);
160  REQUIRE(okay);
161  xmlNodePtr node = doc.getRoot();
162  MagnetizationDensityInput mdi(node);
163 
165 
166  MagnetizationDensity magdens(std::move(mdi), lattice);
167 
168  MagnetizationDensityTests magdenstest;
169  magdenstest.testIntegrationFunctions(magdens);
170  magdenstest.testGrids(magdens);
171 }
172 
173 TEST_CASE("MagnetizationDensity::gridAssignment", "[estimators]")
174 {
175  using namespace testing;
176  int nbintest = 8;
177  ParticleSet::ParticlePos Rtest = {{0.92832101, 0, 1.77067004}, //bin 0
178  {-2.32013211, 0, 5.31201011}, //bin 1
179  {3.48086859, 1.61996772, 1.77067004}, //bin 2
180  {0.23241546, 1.61996772, 5.31201011}, //bin 3
181  {3.48086859, -1.61996772, 1.77067004}, //bin 4
182  {0.23241546, -1.61996772, 5.31201011}, //bin 5
183  {6.03341616, 0, 1.77067004}, //bin 6
184  {2.78496304, 0, 5.31201011}}; //bin 7
185 
187  lattice.R(0, 0) = 5.10509515;
188  lattice.R(0, 1) = -3.23993545;
189  lattice.R(0, 2) = 0.00000000;
190  lattice.R(1, 0) = 5.10509515;
191  lattice.R(1, 1) = 3.23993545;
192  lattice.R(1, 2) = 0.00000000;
193  lattice.R(2, 0) = -6.49690625;
194  lattice.R(2, 1) = 0.00000000;
195  lattice.R(2, 2) = 7.08268015;
196 
197  lattice.BoxBConds = true; //periodic
198  lattice.LR_dim_cutoff = 15;
199  lattice.reset();
200  //Shamelessly stealing this from test_einset.cpp.
201 
202  //Now to construct the input. See ValidMagnetizationDensity.h, item 4 for the actual input.
206  bool okay = doc.parseFromString(mag_input_xml);
207  REQUIRE(okay);
208  xmlNodePtr node = doc.getRoot();
209 
210  MagnetizationDensityInput maginput(node);
211 
212  maginput.calculateDerivedParameters(lattice);
213  MagnetizationDensity magdensity(std::move(maginput), lattice);
214 
215  MagnetizationDensityTests magdenstest;
216  for (int ibin = 0; ibin < nbintest; ibin++)
217  {
218  int mag_bin_x = magdenstest.computeBinAccessor(magdensity, Rtest[ibin], 0);
219  int mag_bin_y = magdenstest.computeBinAccessor(magdensity, Rtest[ibin], 1);
220  int mag_bin_z = magdenstest.computeBinAccessor(magdensity, Rtest[ibin], 2);
221 
222  //Here we go from the flattened spatial grid layout to the grid+spin layout
223  //that we'll dump in data_.
224  int test_bin_x = OHMMS_DIM * ibin + 0;
225  int test_bin_y = OHMMS_DIM * ibin + 1;
226  int test_bin_z = OHMMS_DIM * ibin + 2;
227 
228  CHECK(mag_bin_x == test_bin_x);
229  CHECK(mag_bin_y == test_bin_y);
230  CHECK(mag_bin_z == test_bin_z);
231  }
232 }
233 
234 TEST_CASE("MagnetizationDensity::integralAPI", "[estimators]")
235 {
236  using namespace testing;
237  using WF = WaveFunctionTypes<QMCTraits::ValueType, QMCTraits::FullPrecValueType>;
238  using Real = WF::Real;
239  using Value = WF::Value;
240 
241  Libxml2Document doc_simpsons;
242  Libxml2Document doc_mc;
245  bool okay_simpsons = doc_simpsons.parseFromString(input_xml_simpsons);
246  bool okay_mc = doc_mc.parseFromString(input_xml_mc);
247  REQUIRE(okay_simpsons);
248  REQUIRE(okay_mc);
249  xmlNodePtr node_simpsons = doc_simpsons.getRoot();
250  xmlNodePtr node_mc = doc_mc.getRoot();
251  MagnetizationDensityInput mdi_simpsons(node_simpsons);
252  MagnetizationDensityInput mdi_mc(node_mc);
253 
255 
256  MagnetizationDensity magdens_simpsons(std::move(mdi_simpsons), lattice);
257  MagnetizationDensity magdens_mc(std::move(mdi_mc), lattice);
258 
259 
260  std::vector<Value> ygrid = {0, 1, 8, 27, 64, 125, 216, 343, 512};
261  Value result_simpsons(0.0);
262  Value result_mc(0.0);
263 
264 
265  result_simpsons = magdens_simpsons.integrateMagnetizationDensity(ygrid);
266  result_mc = magdens_mc.integrateMagnetizationDensity(ygrid);
267 
268  CHECK(std::real(result_simpsons) ==
269  Approx(Real(16.2539682539))); //From scipy.integrate. Note, this input file has 64 samples.
270  //Since I only use 9 entries, this integral is internally treated
271  //as from [0 to 2pi*8/63]
272  CHECK(std::real(result_mc) == Approx(Real(10.125))); //Divide sum(ygrid) by nsamples=128
273 }
274 
275 TEST_CASE("MagnetizationDensity::IntegrationTest", "[estimators]")
276 {
277  app_log() << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n";
278  app_log() << "!!!! Evaluate MagDensity !!!!\n";
279  app_log() << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n";
280 
281  //For now, do a small square case.
282  const int nelec = 2;
283  const int norb = 2;
284  using WF = WaveFunctionTypes<QMCTraits::ValueType, QMCTraits::FullPrecValueType>;
285  using Real = WF::Real;
286  using Value = WF::Value;
287  using Grad = WF::Grad;
288  using ValueVector = Vector<Value>;
289  using GradVector = Vector<Grad>;
290  using ValueMatrix = Matrix<Value>;
291  using PropertySetType = OperatorBase::PropertySetType;
292  using MCPWalker = Walker<QMCTraits, PtclOnLatticeTraits>;
293  using Data = MagnetizationDensity::Data;
294  using GradMatrix = Matrix<Grad>;
295  using namespace testing;
296 
297  // O2 test example from pwscf non-collinear calculation.
299  lattice.R(0, 0) = 5.10509515;
300  lattice.R(0, 1) = -3.23993545;
301  lattice.R(0, 2) = 0.00000000;
302  lattice.R(1, 0) = 5.10509515;
303  lattice.R(1, 1) = 3.23993545;
304  lattice.R(1, 2) = 0.00000000;
305  lattice.R(2, 0) = -6.49690625;
306  lattice.R(2, 1) = 0.00000000;
307  lattice.R(2, 2) = 7.08268015;
308 
309  lattice.BoxBConds = true; //periodic
310  lattice.LR_dim_cutoff = 15;
311  lattice.reset();
312  //Shamelessly stealing this from test_einset.cpp.
313 
314  //Now to construct the input. See ValidMagnetizationDensity.h, item 4 for the actual input.
318  bool okay = doc.parseFromString(mag_input_xml);
319  REQUIRE(okay);
320  xmlNodePtr node = doc.getRoot();
321 
322  MagnetizationDensityInput maginput(node);
323 
324  maginput.calculateDerivedParameters(lattice);
325 
326  const SimulationCell simulation_cell(lattice);
327  ParticleSet elec_(simulation_cell);
328 
329  elec_.setName("elec");
330  elec_.create({2});
331 
332  elec_.R[0] = {5, 0, 0};
333  elec_.R[1] = {2.22798, 0, 4.249609};
334  elec_.spins[0] = 1.9;
335  elec_.spins[1] = 2.5410;
336  elec_.setSpinor(true);
337 
338  SpeciesSet& tspecies = elec_.getSpeciesSet();
339  int upIdx = tspecies.addSpecies("u");
340  int chargeIdx = tspecies.addAttribute("charge");
341  tspecies(chargeIdx, upIdx) = -1;
342 
343  elec_.resetGroups();
344  elec_.update();
345  // </steal>
346 
347 
348  //The values for the spinor SPO (up and down channels) were taken from a python script.
349  //Since we are considering only a frozen electron configuration, we lock the spatial parts
350  //of the spinor down to test the spin integration.
351  //
352  //The spin state of these two orbitals corresponds to theta=45, phi=45, so
353  //[cos(theta/2), e^(i*phi)*sin(theta/2)]. This yields an expectation value of
354  //[0.5,0.5,1/sqrt(2)] per electron when done analytically.
355  ValueVector uprow0{Value(0.92387953, 0), Value(0.92387953, 0.)};
356  ValueVector dnrow0{Value(0.27059805, 0.27059805), Value(0.27059805, 0.27059805)};
357  ValueVector uprow1{Value(0.29131988, 0.87674747), Value(0.81078057, 0.44293144)};
358  ValueVector dnrow1{Value(-0.17146777, 0.342119), Value(0.10774051, 0.36720375)};
359 
360  ValueMatrix mup, mdn;
361  mup.resize(nelec, norb);
362  mdn.resize(nelec, norb);
363 
364  for (int iorb = 0; iorb < norb; iorb++)
365  {
366  mup(0, iorb) = uprow0[iorb];
367  mdn(0, iorb) = dnrow0[iorb];
368  mup(1, iorb) = uprow1[iorb];
369  mdn(1, iorb) = dnrow1[iorb];
370  }
371  auto spo_up = std::make_unique<ConstantSPOSet>("ConstantUpSet", nelec, norb);
372  auto spo_dn = std::make_unique<ConstantSPOSet>("ConstantDnSet", nelec, norb);
373 
374  spo_up->setRefVals(mup);
375  spo_dn->setRefVals(mdn);
376  auto spinor_set = std::make_unique<SpinorSet>("ConstSpinorSet");
377  spinor_set->set_spos(std::move(spo_up), std::move(spo_dn));
378 
379  auto dd = std::make_unique<DiracDeterminant<>>(std::move(spinor_set), 0, nelec);
380 
381  std::vector<std::unique_ptr<DiracDeterminantBase>> dirac_dets;
382  dirac_dets.push_back(std::move(dd));
383  auto sd = std::make_unique<SlaterDet>(elec_, std::move(dirac_dets));
384 
385  RuntimeOptions runtime_options;
386  TrialWaveFunction psi(runtime_options);
387  psi.addComponent(std::move(sd));
388 
389  //psi.addComponent(std::move(dd));
390  psi.evaluateLog(elec_); //make sure all intermediates are calculated and initialized.
391 
392  MagnetizationDensity magdensity(std::move(maginput), lattice);
393 
394  //Now to create a fake walker with property set.
395  PropertySetType Observables;
396  MCPWalker my_walker(nelec);
397  my_walker.Weight = 1.0;
398 
399  elec_.saveWalker(my_walker);
400 
401  //Now to create the crowd related quantities:
402  int nwalkers = 2;
403  std::vector<MCPWalker> walkers(nwalkers);
404  std::vector<ParticleSet> psets{elec_, elec_};
405 
406  psets[1].R[0][0] = 0;
407  psets[1].R[0][1] = 0.5;
408  psets[1].R[0][2] = 0;
409  psets[1].R[1][0] = 1;
410  psets[1].R[1][1] = 3;
411  psets[1].R[1][2] = 1;
412 
413  std::vector<UPtr<TrialWaveFunction>> twfcs(2);
414  for (int iw = 0; iw < nwalkers; iw++)
415  twfcs[iw] = psi.makeClone(psets[iw]);
416 
417  auto updateWalker = [](auto& walker, auto& pset_target, auto& trial_wavefunction) {
418  pset_target.update(true);
419  pset_target.donePbyP();
420  trial_wavefunction.evaluateLog(pset_target);
421  pset_target.saveWalker(walker);
422  };
423  for (int iw = 0; iw < nwalkers; iw++)
424  updateWalker(walkers[iw], psets[iw], *(twfcs[iw]));
425 
426  std::vector<QMCHamiltonian> hams;
427  auto ref_walkers(makeRefVector<MCPWalker>(walkers));
428  auto ref_psets(makeRefVector<ParticleSet>(psets));
429  auto ref_twfcs(convertUPtrToRefVector(twfcs));
430  auto ref_hams(makeRefVector<QMCHamiltonian>(hams));
431 
432  FakeRandom rng;
433  magdensity.accumulate(ref_walkers, ref_psets, ref_twfcs, ref_hams, rng);
434 
435  //Now the reference data
436  //
437  //Note. These reference values are derived from an independently written python code.
438  //At this spin configuration, the observable should return these values. When the python code
439  //performs the integration over all spin variables, only then will one agree with analytic results.
440  //More details found in spinor_45deg_magdensity_test.py
441  int datsize = 24;
442  Data ref_data(datsize, 0);
443 
444  //Spinors yield the same value when integrated, but the choice of bin is different between walker 1 and 2.
445  ref_data[0] = -0.97448154;
446  ref_data[1] = -0.37462387;
447  ref_data[2] = 2.36817514;
448 
449  ref_data[12] = -0.97448154;
450  ref_data[13] = -0.37462387;
451  ref_data[14] = 2.36817514;
452 
453  //Spinors yield the same value when integrated, but the choice of bin is different.
454  ref_data[18] = 1.20557377;
455  ref_data[19] = -0.60536469;
456  ref_data[20] = 0.98980165;
457 
458  ref_data[21] = 1.20557377;
459  ref_data[22] = -0.60536469;
460  ref_data[23] = 0.98980165;
461 
462 
463  MagnetizationDensityTests magdenstest;
464  magdenstest.testData(magdensity, ref_data);
465 }
466 #endif
467 } // namespace qmcplusplus
class that handles xmlDoc
Definition: Libxml2Doc.h:76
QMCTraits::RealType real
size_t computeBin(const Position &r, const unsigned int component) const
For a given spatial position r and spin component s, this returns the bin for accumulating the observ...
helper functions for EinsplineSetBuilder
Definition: Configuration.h:43
if(!okay) throw std xmlNodePtr node
std::ostream & app_log()
Definition: OutputManager.h:65
CrystalLattice< OHMMS_PRECISION, OHMMS_DIM > ParticleLayout
Definition: Configuration.h:79
TEST_CASE("complex_helper", "[type_traits]")
Consistent way to get the set of types used in the QMCWaveFunction module, without resorting to ifdef...
xmlNodePtr getRoot()
Definition: Libxml2Doc.h:88
Declaration of OperatorBase.
const char walkers[]
Definition: HDFVersion.h:36
OrbitalSetTraits< ValueType >::ValueVector ValueVector
std::unique_ptr< OperatorEstBase > spawnCrowdClone() const override
standard interface
static RefVector< T > convertUPtrToRefVector(const UPtrVector< T > &ptr_list)
convert a vector of std::unique_ptrs<T> to a refvector<T>
DataLocality data_locality_
locality for accumulation of estimator data.
#define OHMMS_DIM
Definition: config.h:64
ForceBase::Real Real
Definition: ForceBase.cpp:26
CrystalLattice< OHMMS_PRECISION, OHMMS_DIM > lattice
Value integrateBySimpsonsRule(const std::vector< Value > &fgrid, Real gridDx) const
Implementation of Simpson&#39;s 1/3 rule to integrate a function on a uniform grid.
REQUIRE(std::filesystem::exists(filename))
constexpr std::array< std::string_view, 4 > valid_mag_density_input_sections
void generateUniformGrid(std::vector< Real > &sgrid, const Real start, const Real stop) const
Generate a uniform grid between [start,stop] for numerical quadrature.
QTBase::PosType PosType
Definition: Configuration.h:61
void testCopyConstructor(const MagnetizationDensity &magdens)
int computeBinAccessor(const MagnetizationDensity &magdens, const Position &r, const int spin_index)
Declaration of a TrialWaveFunction.
std::vector< QMCT::RealType > Data
bool parseFromString(const std::string_view data)
Definition: Libxml2Doc.cpp:204
Walker< QMCTraits, PtclOnLatticeTraits > MCPWalker
Definition: test_walker.cpp:31
CHECK(log_values[0]==ComplexApprox(std::complex< double >{ 5.603777579195571, -6.1586603331188225 }))
ParticleAttrib< SingleParticlePos > ParticlePos
Definition: Configuration.h:92
void generateRandomGrid(std::vector< Real > &sgrid, RandomBase< FullPrecReal > &rng, Real start, Real stop) const
Generate random grid between [start,stop] for MC integration.
Declaration of DiracDeterminant with a S(ingle)P(article)O(rbital)Set.
void testData(const MagnetizationDensity &magdens, const Data &data)
Declaration of WaveFunctionComponent.
Magnetization density estimator for non-collinear spin calculations.
SpinDensityNew original(std::move(sdi), lattice, species_set)
void testGrids(const MagnetizationDensity &magdens)
void testIntegrationFunctions(const MagnetizationDensity &magdens)
RecordNamedProperty< FullPrecRealType > PropertySetType
define PropertyList_t
Definition: Configuration.h:69