HILA
Loading...
Searching...
No Matches
sun_realtime.cpp
1#include "hila.h"
2#include "gauge/staples.h"
3#include "gauge/degauss.h"
4
5#ifndef NSU
6 #error "!!! Specify which SU(N) in Makefile: eg. -DNSU=3"
7#endif
8
9using SUN = SU<NSU, double>;
10
11// Output stream for results
12std::ofstream measureFile;
13
14///////////////////////////////////////////////////////////////////////////////////////////////
15/* Hamiltonian time evolution for gauge and electric fields. 'delta' is the time difference. */
16template <typename group>
17void update_E(const GaugeField<group> &U, VectorField<Algebra<group>> &E,
18 double delta) {
19
20 Field<group> staple;
21
22 foralldir (d) {
23 staplesum(U, staple, d);
24
25 onsites (ALL) {
26 E[d][X] -= delta * (U[d][X] * staple[X].dagger()).project_to_algebra();
27 }
28 }
29}
30
31template <typename group>
32void update_U(GaugeField<group> &U, const VectorField<Algebra<group>> &E,
33 double delta) {
34
35 foralldir (d) {
36 onsites (ALL)
37 U[d][X] = exp(E[d][X] * delta) * U[d][X];
38 }
39}
40
41///////////////////////////////////////////////////////////////////////////////////////////////
42
43template <typename group>
44void regroup_gauge(GaugeField<group> &U) {
45 foralldir (d) {
46 onsites (ALL)
47 U[d][X].reunitarize();
48 }
49}
50
51// Total plaquette action (without beta prefactor)
52template <typename group>
53double measure_plaq(const GaugeField<group> &U) {
54
56 plaq.allreduce(false).delayed(true);
57
58 foralldir (dir1)
59 foralldir (dir2)
60 if (dir1 < dir2) {
61 onsites (ALL) {
62 plaq += group::size() - real(trace(U[dir1][X] * U[dir2][X + dir1] *
63 U[dir1][X + dir2].dagger() *
64 U[dir2][X].dagger()));
65 }
66 }
67 return plaq.value();
68}
69
70
71/* Calculate the lattice counterpart of B_i(x) = -1/2 eps_{ijk} F_jk(x) = -sum_{j<k} eps_{ijk} F_jk(x) everywhere. */
72/* We extract the lattice field strength tensor from minimal clover terms:
73* Q_{jk} = P_{jk} + P_{k,-j} + P_{-j,-k} + P_{-k,j}, then
74* i g F_{jk} = (Q_{jk} - Q_{kj}) / (8a^2).
75* Because Q_{kj} = Q^+_{jk}, we only need Q_{jk} for j<k.
76* Here I do NOT project the result to algebra in order to avoid unnecessary operations
77* => B here is of the GaugeField type.
78* Note that I do not subtract the trace from F_{jk}, so it's not necessarily traceless.
79* But the trace part drops out from the topological charge anyway since Tr E_i = 0. */
80template <typename group>
81void get_magnetic_field(const GaugeField<group> &U, GaugeField<group> &B) {
82
83 foralldir(d1) onsites(ALL) B[d1][X] = 0;
84
85 // "i,j,k" with j<k only
86 foralldir(d1) {
87
88 foralldir(d2) foralldir(d3) if (d1 != d2 && d1 != d3 && d2 < d3) {
89
90 /* Get clovers in the plane orthogonal to 'd1': */
92 onsites(ALL) {
93 group P1, P2, P3, P4;
94 P1 = U[d2][X] * U[d3][X + d2] * U[d2][X + d3].dagger() * U[d3][X].dagger();
95 P2 = U[d3][X] * U[d2][X + d3 - d2].dagger() * U[d3][X - d2].dagger() * U[d2][X - d2];
96 P3 = U[d2][X - d2].dagger() * U[d3][X - d2 - d3].dagger() * U[d2][X - d3 - d2] * U[d3][X - d3];
97 P4 = U[d3][X - d3].dagger() * U[d2][X - d3] * U[d3][X + d2 - d3] * U[d2][X].dagger();
98
99 Q[X] = P1 + P2 + P3 + P4;
100 }
101
102 // int sign = (d1-d2)*(d2-d3)*(d3-d1)/2; // epsilon tensor in 3 dimensions
103 int sign;
104 // Here d2 < d3 always
105 if (d2 > d1) {
106 sign = 1;
107 } else if (d3 > d1) {
108 sign = -1;
109 } else {
110 sign = 1;
111 }
112
113 // Get local magnetic field (actually g a^2 B_i, anti-Hermitian algebra)
114 onsites(ALL) {
115 B[d1][X] += -(1.0/ 8.0) * sign * (Q[X] - Q[X].dagger());
116 }
117
118 } // end foralldir d2 d3
119
120 } // end d1; mag. field done
121
122}
123
124
125/* Measure classical topological charge density chi everywhere */
126template <typename group>
127void calc_topoCharge(const GaugeField<group> &U, const VectorField<Algebra<group>> &E, Field<double> &result) {
128
129 double c_chi = 1.0 / (64.0 * M_PI*M_PI);
130 onsites(ALL) result[X] = 0;
131
132 // Magnetic field, but not projected to algebra for better performance:
134 get_magnetic_field(U, B);
135
136 /* Now a^4 chi = 16 c_chi a^4 g^2 Tr (E_i B_i) with antiHermitian E_i, B_i,
137 * and the lattice fields are actually g a^2 E_i and g a^2 B_i. */
138 foralldir(d1) onsites(ALL) {
139 result[X] += 16.0 * c_chi * real( trace(E[d1][X].expand() * B[d1][X]) );
140 // this trace is automatically real
141 }
142
143}
144
145
146static double degauss_quality = 1e-12;
147
148// Do the measurements. Here 't' labels the Hamiltonian time
149template <typename group>
150void measure_stuff(GaugeField<group> &U, VectorField<Algebra<group>> &E, int trajectory, double t) {
151
152 auto plaq = measure_plaq(U); // N - Tr Re P_ij
153
154 double e2 = 0.0;
155 foralldir (d)
156 e2 += E[d].squarenorm();
157 e2 /= 2; // now e2 = Tr E_i^2
158
159 // total energy ("action") times g^2 a T
160 double energy = e2 + 2.0 * plaq;
161
163 get_gauss_violation(U, E, g);
164 auto viol = g.squarenorm();
165
166 /* Measure 'improved' or 'symmetrized' charge density: The way E_i appears in the EOM suggests
167 * that we should identify E_i(x) as living at link midpoint, while the mag. field B_i(x) is local to x.
168 * To measure E.B at x, we take covariant average of E: E^{imp}_i(x) = 0.5 * (E_i(x) + U^+_i(x-i)E_i(x-i)U_i(x-i)) */
169 Field<double> chi;
171 foralldir(d1) onsites(ALL) {
172 E_imp[d1][X] = 0.5 * (E[d1][X] +
173 (U[d1][X-d1].dagger() * E[d1][X-d1].expand() * U[d1][X-d1]).project_to_algebra() );
174 }
175 calc_topoCharge(U, E_imp, chi);
176
177 double chi_avg = 0.0;
178 onsites(ALL)
179 chi_avg += chi[X];
180
181 chi_avg /= lattice.volume();
182
183
184 char buf[1024];
185 sprintf(buf, "%d %.10g %.8g %.8g %.8g %.8g %.8g", trajectory, t, plaq, e2, viol, energy, chi_avg);
186 if (hila::myrank() == 0) measureFile << std::string(buf) << "\n";
187}
188
189
190/* Thermalization according to ref. hep-ph/9603384 (see Appendix A).
191* Gauss constraint is enforced by degauss() routine
192* that operates as described around eq. (38) in the reference. */
193template <typename group>
194void thermalize(GaugeField<group> &U, VectorField<Algebra<group>> &E, double g2Ta,
195 int iterations, double dt) {
196
197 regroup_gauge(U);
198
199 static hila::timer therm_timer("Thermalization");
200
201 therm_timer.start();
202 for (int loop = 0; loop < iterations; loop++) {
203 foralldir (d)
204 onsites (ALL)
205 E[d][X].gaussian_random(sqrt(g2Ta));
206
207 degauss(U, E, degauss_quality);
208
209 // 1/2 timestep for U first
210 update_U(U, E, dt / 2);
211 // do 1 time unit of evolution with leapfrog
212 for (int steps = 0; steps < 1.0 / dt - 1; steps++) {
213 update_E(U, E, dt);
214 update_U(U, E, dt);
215 }
216 // and bring U and E to the same time value
217 update_E(U, E, dt);
218 update_U(U, E, dt / 2);
219
220 /*
221 double pl = measure_plaq(U);
222 double e2 = 0;
223 foralldir (d) { e2 += E[d].squarenorm(); }
224
225 hila::out0 << "THERM: Plaq: " << pl << " E^2 " << e2 << " action "
226 << e2 / 2 + 2 * pl << '\n';
227 */
228
229 regroup_gauge(U);
230 }
231 therm_timer.stop();
232}
233
234////////////////////////////////////////////////////////////////
235
236template <typename group>
237void do_trajectory(GaugeField<group> &U, VectorField<Algebra<group>> &E, int trajectory,
238 int trajlen, int measure_interval, double dt) {
239
240 // Measure first at time t=0:
241 double t = 0.0;
242 measure_stuff(U, E, trajectory, t);
243
244 // Then evolve until we reach t = trajlen*dt
245 for (int n = 0; n < trajlen; n += measure_interval) {
246 update_U(U, E, dt / 2);
247 // do 2 time units of evolution with leapfrog
248 for (int steps = 0; steps < measure_interval - 1; steps++) {
249 update_E(U, E, dt);
250 update_U(U, E, dt);
251 }
252 // and bring U and E to the same time value
253 update_E(U, E, dt);
254 update_U(U, E, dt / 2);
255
256 t += dt * measure_interval;
257 measure_stuff(U, E, trajectory, t);
258 }
259}
260
261/////////////////////////////////////////////////////////////////
262
263
264
265int main(int argc, char **argv) {
266
267 // hila::initialize should be called as early as possible
268 hila::initialize(argc, argv);
269
270 hila::out0 << "---- Hamiltonian time evolution for SU(N) theory, using N=" << NSU << " ----\n";
271
272 // hila provides an input class hila::input, which is
273 // a convenient way to read in parameters from input files.
274 // parameters are presented as key - value pairs, as an example
275 // " lattice size 64, 64, 64 "
276 // is read below.
277 //
278 // Values are broadcast to all MPI nodes.
279 //
280 // .get() -method can read many different input types,
281 // see file "input.h" for documentation
282
283 hila::input par("parameters");
284
285 CoordinateVector lsize;
286 lsize = par.get("lattice size"); // reads NDIM numbers
287
288 double g2Ta = par.get("g^2 Ta");
289 double dt = par.get("dt");
290 int trajlen = par.get("trajectory length");
291 int n_traj = par.get("number of trajectories");
292 int measure_interval = par.get("measurement interval");
293 int n_thermal = par.get("thermalisation");
294 int n_thermal_start = par.get("thermalisation start");
295 long seed = par.get("random seed");
296 std::string meas_fname = par.get("measurement file");
297
298 par.close(); // file is closed also when par goes out of scope
299
300 // setting up the lattice is convenient to do after reading
301 // the parameter
302 lattice.setup(lsize);
303
304 // We need random number here
305 hila::seed_random(seed);
306
307
308 if (hila::myrank() == 0) {
309 measureFile.open(meas_fname, std::ios_base::app);
310 if (!measureFile) {
311 hila::out0 << "!!! Error opening measurement file\n";
313 }
314 }
315
316 // Print measurement labels
317 if (hila::myrank() == 0) {
318 std::ofstream labelFile;
319 std::string labelFileName = "labels_" + meas_fname;
320 labelFile.open(labelFileName);
321 if (!labelFile) {
322 hila::out0 << "!!! Error opening file " << labelFileName << "\n";
324 }
325 labelFile << "1 trajectory\n" << "2 time (lattice units)\n" << "3 plaq avg: sum_i<j (N - Tr Re P_ij)\n"
326 << "4 Tr E_i^2\n" << "5 Gauss violation (G^a G^a over the whole system)\n" << "6 total energy\n"
327 << "7 average chi\n";
328
329 labelFile.close();
330 }
331
332 // Alloc gauge field and momenta (E)
335 foralldir(d) onsites(ALL) {
336 U[d][X] = 0;
337 E[d][X] = 0;
338 }
339
340 // some initial noise for gauge field
341 foralldir (d) {
342 onsites (ALL) {
343 U[d][X].gaussian_random(0.3).reunitarize();
344 }
345 }
346
347
348 thermalize(U, E, g2Ta, n_thermal_start, dt);
349
350 for (int trajectory = 0; trajectory < n_traj; trajectory++) {
351 thermalize(U, E, g2Ta, n_thermal, dt);
352 if (trajectory % 500 == 0) {
353 hila::out0 << "Trajectory " << trajectory << "\n";
354 }
355 do_trajectory(U, E, trajectory, trajlen, measure_interval, dt);
356 }
357
358
359 // done
360 if (hila::myrank() == 0) {
361 measureFile.close();
362 }
363
365}
Array< n, m, T > exp(Array< n, m, T > a)
Exponential.
Definition array.h:1059
Array< n, m, T > sqrt(Array< n, m, T > a)
Square root.
Definition array.h:1039
Array< n, m, hila::arithmetic_type< T > > real(const Array< n, m, T > &arg)
Return real part of Array.
Definition array.h:688
Definition su2.h:7
The field class implements the standard methods for accessing Fields. Hilapp replaces the parity acce...
Definition field.h:61
Field< A > dagger() const
Returns dagger or Hermitian conjugate of Field depending on how it is defined for Field type T.
Definition field.h:1123
double squarenorm() const
Squarenorm.
Definition field.h:1083
Gauge field class.
Definition gaugefield.h:22
Special reduction class: enables delayed and non-blocking reductions, which are not possible with the...
Definition reduction.h:14
const T value()
Return value of the reduction variable. Wait for the comms if needed.
Definition reduction.h:157
Reduction & allreduce(bool b=true)
allreduce(bool) turns allreduce on or off. By default on.
Definition reduction.h:130
Reduction & delayed(bool b=true)
deferred(bool) turns deferred on or off. By default turns on.
Definition reduction.h:148
Class for SU(N) matrix.
Definition sun_matrix.h:110
hila::input - Class for parsing runtime parameter files.
Definition input.h:52
void setup(const CoordinateVector &siz)
General lattice setup.
Definition lattice.cpp:33
Complex< T > dagger(const Complex< T > &val)
Return dagger of Complex number.
Definition cmplx.h:1223
#define foralldir(d)
Macro to loop over (all) Direction(s)
Definition coordinates.h:78
constexpr Parity ALL
bit pattern: 011
int myrank()
rank of this node
Definition com_mpi.cpp:234
std::ostream out0
This writes output only from main process (node 0)
void initialize(int argc, char **argv)
Read in command line arguments. Initialise default stream and MPI communication.
void seed_random(uint64_t seed, bool device_rng=true)
Seed random generators with 64-bit unsigned value. On MPI shuffles the seed so that different MPI ran...
Definition random.cpp:86
void finishrun()
Normal, controlled exit - all nodes must call this. Prints timing information and information about c...
void staplesum(const GaugeField< T > &U, Field< T > &staples, Direction d1, Parity par=ALL)
Sum the staples of link matrices to direction dir.
Definition staples.h:28
void do_trajectory(GaugeField< group > &U, const parameters &p)
Evolve gauge field.
void measure_stuff(const GaugeField< group > &U, const parameters &p)
Measures Polyakov lines and Wilson action.
Definition suN_gauge.cpp:41