The Heisenberg Example. But threaded!
The O(3) Heisenberg model is a slightly more advanced model on a continuous state space. This demo also gives an example of how to inject a self-defined Wolff Cluster update for your self-defined model into MARQOV. Additionally we show in this example how to use the MARQOV scheduler in order to have automatic scheduling of the jobs derived from your parameter space sampling.
1#include <iostream>
2#include <array>
3#include <tuple>
4#include <vector>
5
6//include the MARQOV library
7#undef MPIMARQOV
8#include "libmarqov.h"
9
10//include the RegularLattice
11#include "../src/lattice/regular_hypercubic.h"
12
13//include some predefined observables, e.g. the magnetization and the energy
14#include "../src/hamiltonian/util/observables.h"
15
16
17// Define interaction term for the Heisenberg model
18class MyHeisenberg_interaction
19{
20 public:
21 MyHeisenberg_interaction(const double J) : J(J) {}
22 std::array<double, 3> get (const std::array<double, 3>& phi) {return phi;};
23 const double J;
24};
25
26class MySimpleHeisenberg
27{
28 public:
29
30 // The spin dimension of the Heisenberg or O(3) model
31 constexpr static int SymD = 3;
32
33 // Define the state vector that this model will use.
34 // For the Heisenberg model this is a three-dimensional unit vector
35 typedef std::array<double, SymD> StateVector;
36
37 // Parameters
38 double J; // The coupling constant
39 const std::string name; // every Hamiltonian MUST have a name, this is required for the HDF5 output
40
41 // Hamiltonian terms
42 // here this is only the canonical O(3) interaction, defined above
43 std::array<MyHeisenberg_interaction*, 1> interactions = {new MyHeisenberg_interaction(J)};
44
45 // Constructor
46 MySimpleHeisenberg(double J) : J(J), name("MySimpleHeisenberg"), obs_e(*this){}
47 ~MySimpleHeisenberg() {delete interactions[0];}
48
49 // Observables
50 Magnetization obs_m;
51 Energy<MySimpleHeisenberg> obs_e{*this};
52 std::pair<Magnetization, Energy<MySimpleHeisenberg> > observables{obs_m, obs_e};
53};
54
55namespace MARQOV
56{
57 template <class Lattice>
58 struct Wolff<MySimpleHeisenberg, Lattice>
59 {
60 std::vector<int> cstack = std::vector<int>(4096/sizeof(int), 0);///< the size of the stack is meant to be preserved across different cluster processes.
61
62 template <class RNG, class StateSpace>
63 inline int move(const MySimpleHeisenberg& ham, const Lattice& grid, StateSpace& statespace, RNG& rng, double beta, int rsite)
64 {
65 typedef MySimpleHeisenberg Hamiltonian;
66 typedef typename Hamiltonian::StateVector StateVector;
67 constexpr static int SymD = Hamiltonian::SymD;
68
69 int q = 0;
70 auto& seed = statespace[rsite];
71 cstack[q] = rsite;
72
73 int rdir = rng.integer(SymD); // random Cartesian direction
74 seed[rdir] *= -1;
75
76 int clustersize = 1;
77 int current = 0;
78
79 // plain Heisenberg model has only one interaction term
80 const auto gcpl = ham.interactions[0]->J;
81
82 while (q>=0)
83 {
84 current = cstack[q];
85 q--;
86
87 const auto nbrs = grid.nbrs(0, current);
88 if(q + nbrs.size() > cstack.size())
89 cstack.resize(2*cstack.size());
90 for (std::size_t i = 0; i < nbrs.size(); ++i)
91 {
92 const auto currentnbr = nbrs[i];
93 StateVector& candidate = statespace[currentnbr];
94
95 const auto lcpl = statespace[current][rdir] * candidate[rdir];
96 const auto cpl = gcpl*lcpl;
97
98 if (wolff_update_accepted(cpl, beta, rng))
99 {
100 q++;
101 clustersize++;
102 cstack[q] = currentnbr;
103 candidate[rdir] = -candidate[rdir];
104 }
105 }
106 }
107 return clustersize;
108 }
109 };
110}
111
112using namespace std;
113using namespace MARQOV;
114
115int main()
116{
117 std::cout<<"Welcome to the MARQOV Heisenberg test case! ";
118 std::cout<<"This test is the minimum example that features a self-defined Heisenberg model and utlizes ";
119 std::cout<<"the multithread scheduler."<<std::endl;
120 // Initialize the lattice
121 int L = 64;
122 int dim = 3;
123 RegularHypercubic mylatt(L, dim);
124
125 // Set Monte Carlo parameters using MARQOV::Config
126 MARQOV::Config mp("./"); // output path
127 mp.setnmetro(5); // number of Metropolis sweeps per EMCS
128 mp.setncluster(10); // number of Wolff updates per EMCS
129 mp.setwarmupsteps(5); // number of EMCS for warmup
130 mp.setgameloopsteps(3); // number of EMCS for production
131 mp.setloglevel(RELEASE); // set the log level
132
133 // Set the Hamiltonian parameters, J, and the inverse temperature beta
134 double beta = 0.66;
135 double J = -1;
136 auto hp = make_tuple(beta, J);
137
138 // Let's create some parameters tuples, a temperature scan for the scheduler to work on
139
140 // Prepare an array of parameter tuples
141 auto args = make_tuple(std::ref(mylatt), mp, hp);
142 vector<decltype(args)> v;
143
144 // Fill
145 for(int j = 0; j < 4; ++j)
146 {
147 hp = make_tuple(beta+j*0.1, J);
148 v.push_back(make_tuple(std::ref(mylatt), mp, hp));
149 }
150
151 // Set up the MARQOV schedular
152 auto sched = makeScheduler<MySimpleHeisenberg, RegularHypercubic>(args, 10);
153 sched.setloglevel(RELEASE);
154 // Feed parameters to the scheduler which creates the simulations
155 for(auto p : v)
156 sched.createSimfromParameter(p);
157 // Run
158 sched.start();
159}