HILA
Loading...
Searching...
No Matches
fermion_field.h
1#ifndef FERMION_FIELD_H
2#define FERMION_FIELD_H
3
4#include "gauge_field.h"
5#include "dirac/Hasenbusch.h"
6#include "dirac/conjugate_gradient.h"
7#include "MRE_guess.h"
8#include <cmath>
9
10/// Define the action of a pseudofermion for HMC
11///
12/// Implements methods for calculating the current action
13/// and the force (derivative with respect to the gauge
14/// field).
15///
16/// Includes an implementation of the MRE initial guess,
17/// which is calculated in the base of a few previous
18/// solutions. Using this requires a higher accuracy,
19/// since the initial guess is not time reversible.
20///
21template <typename gauge_field, typename DIRAC_OP>
23 public:
24 using vector_type = typename DIRAC_OP::vector_type;
26 gauge_field &gauge;
27 DIRAC_OP &D;
29
30 /// We save a few previous invertions to build an initial guess.
31 /// old_chi contains a list of these
32 int MRE_size = 0;
33 std::vector<Field<vector_type>> old_chi_inv;
34
35 void setup(int mre_guess_size) {
36#if NDIM > 3
37 chi.set_boundary_condition(e_t, hila::bc::ANTIPERIODIC);
38 chi.set_boundary_condition(-e_t, hila::bc::ANTIPERIODIC);
39#endif
40 MRE_size = mre_guess_size;
41 old_chi_inv.resize(MRE_size);
42 for (int i = 0; i < MRE_size; i++) {
43 old_chi_inv[i][ALL] = 0;
44 }
45 }
46
47 fermion_action(DIRAC_OP &d, gauge_field &g) : D(d), gauge(g) {
48 chi = 0.0; // Allocates chi and sets it to zero
49 setup(0);
50 }
51
52 fermion_action(DIRAC_OP &d, gauge_field &g, int mre_guess_size) : D(d), gauge(g) {
53 chi = 0.0; // Allocates chi and sets it to zero
54 setup(mre_guess_size);
55 }
56
57 fermion_action(fermion_action &fa) : gauge(fa.gauge), D(fa.D) {
58 chi = fa.chi; // Copies the field
59 setup(fa.MRE_size);
60 }
61
62 /// Build an initial guess for the fermion matrix inversion
63 /// by inverting first in the limited space of a few previous
64 /// solutions. These are saved in old_chi.
66 psi[ALL] = 0;
67 if (MRE_size > 0) {
68 MRE_guess(psi, chi, D, old_chi_inv);
69 }
70 // If the gauge type is double precision, solve first in single precision
71 if constexpr (std::is_same<double, typename gauge_field::basetype>::value) {
72 hila::out0 << "Starting with single precision inversion\n";
73
74 auto single_precision = gauge.get_single_precision();
75 typename DIRAC_OP::type_flt D_flt(D, single_precision);
77 c[ALL] = chi[X];
78 p[ALL] = psi[X];
79 CG inverse(D_flt);
80 inverse.apply(c, p);
81
82 D_flt.apply(p, t1);
83 D_flt.dagger(t1, t2);
84 psi[ALL] = p[X];
85 }
86 }
87
88 /// Return the value of the action with the current
89 /// field configuration
90 double action() {
93 CG<DIRAC_OP> inverse(D);
94 double action = 0;
95
96 gauge.refresh();
97
98 psi = 0;
99 initial_guess(chi, psi);
100 inverse.apply(chi, psi);
101 onsites(D.par) { action += chi[X].rdot(psi[X]); }
102 return action;
103 }
104
105 /// Calculate the action as a field of double precision numbers
109 CG<DIRAC_OP> inverse(D);
110
111 gauge.refresh();
112
113 psi = 0;
114 initial_guess(chi, psi);
115 inverse.apply(chi, psi);
116 onsites(D.par) {
117 S[X] += chi[X].rdot(psi[X]);
118 }
119 }
120
121 /// Generate a pseudofermion field with a distribution given
122 /// by the action chi 1/(D_dagger D) chi
126 gauge.refresh();
127
128 onsites(D.par) {
129 psi[X].gaussian_random();
130 }
131 D.dagger(psi, chi);
132 }
133
134 /// Add new solution to the list for MRE
136 if (MRE_size > 0) {
137 for (int i = 1; i < MRE_size; i++) {
138 old_chi_inv[i] = old_chi_inv[i - 1];
139 }
140 old_chi_inv[0] = psi;
141 }
142 }
143
144 /// Update the momentum with the derivative of the fermion
145 /// action
146 void force_step(double eps) {
147 Field<vector_type> psi, Mpsi;
149 Mpsi.copy_boundary_condition(chi);
150 Field<momtype> force[NDIM], force2[NDIM];
151
152 CG<DIRAC_OP> inverse(D);
153 gauge.refresh();
154
155 hila::out0 << "base force\n";
156 initial_guess(chi, psi);
157 inverse.apply(chi, psi);
159
160 D.apply(psi, Mpsi);
161
162 D.force(Mpsi, psi, force, 1);
163 D.force(psi, Mpsi, force2, -1);
164
165 foralldir(dir) { force[dir][ALL] = -eps * (force[dir][X] + force2[dir][X]); }
166 gauge.add_momentum(force);
167 }
168};
169
170/// The Hasenbusch method for updating fermion fields:
171/// Split the Dirac determinant into two parts,
172/// D_h1 = D + mh and
173/// D_h2 = D * 1 / (D + mh)^dagger
174///
175///
176/// This is the first action term, with D_h1 = D + mh.
177/// Since the only real difference here is an addition
178/// to the original operator, we can use fermion_action
179/// with a different operator.
180///
181template <typename gauge_field, typename DIRAC_OP>
183 public:
186 double _mh;
187
188 Hasenbusch_action_1(DIRAC_OP &d, gauge_field &g, double mh)
189 : _mh(mh), D_h(d, mh), base_action(D_h, g) {}
190 Hasenbusch_action_1(DIRAC_OP &d, gauge_field &g, double mh, int mre_guess_size)
191 : _mh(mh), D_h(d, mh), base_action(D_h, g, mre_guess_size) {}
192
194 : _mh(fa._mh), D_h(fa.D_h), base_action(fa.base_action) {}
195
196 double action() { return (base_action.action()); }
197 void action(Field<double> &S) { base_action.action(S); }
199 void force_step(double eps) { base_action.force_step(eps); }
200};
201
202/// The second Hasenbusch action term, D_h2 = D/(D^dagger + mh).
203/// The force and action of the second term are significantly
204/// different from the standard fermion action and are implemented
205/// here.
206template <typename gauge_field, typename DIRAC_OP>
208 public:
209 using vector_type = typename DIRAC_OP::vector_type;
211 gauge_field &gauge;
212 DIRAC_OP D;
214 double mh;
216
217 // We save a few previous invertions to build an initial guess.
218 // old_chi contains a list of these
219 int MRE_size = 0;
220 std::vector<Field<vector_type>> old_chi_inv;
221
222 void setup(int mre_guess_size) {
223#if NDIM > 3
224 chi.set_boundary_condition(e_t, hila::bc::ANTIPERIODIC);
225 chi.set_boundary_condition(-e_t, hila::bc::ANTIPERIODIC);
226#endif
227 MRE_size = mre_guess_size;
228 old_chi_inv.resize(MRE_size);
229 for (int i = 0; i < MRE_size; i++) {
230 old_chi_inv[i][ALL] = 0;
231 }
232 }
233
234 Hasenbusch_action_2(DIRAC_OP &d, gauge_field &g, double _mh)
235 : mh(_mh), D(d), D_h(d, _mh), gauge(g) {
236 chi = 0.0; // Allocates chi and sets it to zero
237 setup(0);
238 }
239 Hasenbusch_action_2(DIRAC_OP &d, gauge_field &g, double _mh, int mre_guess_size)
240 : mh(_mh), D(d), D_h(d, _mh), gauge(g) {
241 chi = 0.0; // Allocates chi and sets it to zero
242 setup(mre_guess_size);
243 }
244
246 : mh(fa.mh), D(fa.D), D_h(fa.D_h), gauge(fa.gauge) {
247 chi = fa.chi; // Copies the field
248 setup(fa.MRE_size);
249 }
250
251 /// Return the value of the action with the current
252 /// Field configuration
253 double action() {
258 double action = 0;
259
260 gauge.refresh();
261 CG<DIRAC_OP> inverse(D);
262
263 v[ALL] = 0;
264 D_h.dagger(chi, psi);
265 inverse.apply(psi, v);
266 D.apply(v, psi);
267 onsites(D.par) {
268 action += squarenorm(psi[X]);
269 }
270
271 return action;
272 }
273
274 /// Return the action as a field of double precision numbers
280
281 gauge.refresh();
282 CG inverse(D);
283
284 v[ALL] = 0;
285 D.dagger(chi, psi);
286 inverse.apply(psi, v);
287 D.apply(v, psi);
288 onsites(EVEN) { S[X] += squarenorm(psi[X]); }
289 }
290
291 /// Generate a pseudofermion field with a distribution given
292 /// by the action chi 1/(D_dagger D) chi
298 CG inverse_h(D_h); // Applies 1/(D_h^dagger D_h)
299 gauge.refresh();
300
301 psi[ALL] = 0;
302 onsites(D.par) {
303 psi[X].gaussian_random();
304 }
305 D.dagger(psi, v);
306 psi[ALL] = 0;
307 inverse_h.apply(v, psi);
308 D_h.apply(psi, chi);
309 }
310
311 /// Build an initial guess for the fermion matrix inversion
312 /// by inverting first in the limited space of a few previous
313 /// solutions. These are saved in old_chi.
315 psi[ALL] = 0;
316 if (MRE_size > 0) {
317 MRE_guess(psi, chi, D, old_chi_inv);
318 }
319 // If the gauge type is double precision, solve first in single precision
320 if constexpr (std::is_same<double, typename gauge_field::basetype>::value) {
321 hila::out0 << "Starting with single precision inversion\n";
322
323 auto single_precision = gauge.get_single_precision();
324 typename DIRAC_OP::type_flt D_flt(D, single_precision);
326 c[ALL] = chi[X];
327 p[ALL] = psi[X];
328 CG inverse(D_flt);
329 inverse.apply(c, p);
330
331 D_flt.apply(p, t1);
332 D_flt.dagger(t1, t2);
333 psi[ALL] = p[X];
334 }
335 }
336
337 /// Add new solution to the list
339 if (MRE_size > 0) {
340 for (int i = 1; i < MRE_size; i++) {
341 old_chi_inv[i] = old_chi_inv[i - 1];
342 }
343 old_chi_inv[0] = psi;
344 }
345 }
346
347 /// Update the momentum with the derivative of the fermion
348 /// action
349 void force_step(double eps) {
350 Field<vector_type> psi, Mpsi;
351 Field<vector_type> Dhchi;
353 Mpsi.copy_boundary_condition(chi);
354 Dhchi.copy_boundary_condition(chi);
355 Field<momtype> force[NDIM], force2[NDIM];
356
357 CG<DIRAC_OP> inverse(D);
358 gauge.refresh();
359
360 D_h.dagger(chi, Dhchi);
361
362 initial_guess(Dhchi, psi);
363 inverse.apply(Dhchi, psi);
365
366 D.apply(psi, Mpsi);
367
368 Mpsi[D.par] = Mpsi[X] - chi[X];
369
370 D.force(Mpsi, psi, force, 1);
371 D.force(psi, Mpsi, force2, -1);
372
373 foralldir(dir) { force[dir][ALL] = -eps * (force[dir][X] + force2[dir][X]); }
374 gauge.add_momentum(force);
375 }
376};
377
378#endif
hila::arithmetic_type< T > squarenorm(const Array< n, m, T > &rhs)
Return square norm of Array.
Definition array.h:957
The conjugate gradient operator. Applies the inverse square of an operator on a vector.
void apply(Field< vector_type > &in, Field< vector_type > &out)
The field class implements the standard methods for accessing Fields. Hilapp replaces the parity acce...
Definition field.h:61
void set_boundary_condition(Direction dir, hila::bc bc)
Set the boundary condition in a given Direction (periodic or antiperiodic)
Definition field.h:580
Field< A > dagger() const
Returns dagger or Hermitian conjugate of Field depending on how it is defined for Field type T.
Definition field.h:1160
void copy_boundary_condition(const Field< A > &rhs)
Copy the boundary condition from another field.
Definition field.h:650
void force_step(double eps)
double action()
Calculate and return the action.
void initial_guess(Field< vector_type > &chi, Field< vector_type > &psi)
void force_step(double eps)
void action(Field< double > &S)
Return the action as a field of double precision numbers.
void save_new_solution(Field< vector_type > &psi)
Add new solution to the list.
Matrix class which defines matrix operations.
Definition matrix.h:1679
void draw_gaussian_fields()
void action(Field< double > &S)
Calculate the action as a field of double precision numbers.
void save_new_solution(Field< vector_type > &psi)
Add new solution to the list for MRE.
void force_step(double eps)
void initial_guess(Field< vector_type > &chi, Field< vector_type > &psi)
virtual void refresh()
Recalculate represented or smeared field.
Definition gauge_field.h:27
void add_momentum(Field< SquareMatrix< N, Complex< basetype > > > *force)
constexpr Parity EVEN
bit pattern: 001
#define foralldir(d)
Macro to loop over (all) Direction(s)
Definition coordinates.h:78
constexpr Parity ALL
bit pattern: 011
std::ostream out0
This writes output only from main process (node 0)