Skip to content

Commit 88879af

Browse files
committed
Adds scalar field code
1 parent d481c5e commit 88879af

9 files changed

+4980
-0
lines changed

Diff for: pybin/scalar_field_aughmc.py

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""
2+
HMC for (complex) scalar fields in arbitrary dimensions.
3+
"""
4+
5+
import argparse
6+
7+
from scalar_field.scalar_field_hmc import *
8+
9+
10+
def main(out_prefix, init_cfg, action, eps, n_leap, iters, skip, therm):
11+
ensemble, actions = aug_hmc_ensemble(init_cfg, action, eps, n_leap, iters, skip, therm)
12+
print('Ensemble shape = {}.'.format(np.array(ensemble).shape))
13+
fname = out_prefix+'.npy'
14+
print('Writing ensemble to {}.'.format(fname))
15+
np.save(fname, np.array(ensemble))
16+
fname = out_prefix+'.S.npy'
17+
print('Writing actions to {}.'.format(fname))
18+
np.save(fname, np.array(actions))
19+
20+
if __name__ == "__main__":
21+
parser = argparse.ArgumentParser(
22+
description="HMC (with sign flip aug) ensemble for scalar field.")
23+
# action
24+
parser.add_argument('--m2', type=float, required=True,
25+
help='Lattice M^2 setting inverse spacing')
26+
parser.add_argument('--lam', type=float, default=0.0,
27+
help='Coeffient lambda of phi^4 term')
28+
parser.add_argument('--complex_type', action='store_true',
29+
help='Use complex phi fields')
30+
# logistics
31+
parser.add_argument('--out_prefix', type=str, required=True,
32+
help='Output prefix for ensemble')
33+
parser.add_argument('--eps', type=float, required=True,
34+
help='HMC traj eps')
35+
parser.add_argument('--n_leap', type=int, required=True,
36+
help='HMC traj leaps')
37+
parser.add_argument('--iters', type=int, required=True,
38+
help='Number of measurements')
39+
parser.add_argument('--therm', type=int, required=True,
40+
help='Number of therm iters')
41+
parser.add_argument('--skip', type=int, required=True,
42+
help='Number skipped between meas')
43+
parser.add_argument('--seed', type=int)
44+
parser.add_argument('--seed_file', type=str)
45+
# lattice
46+
parser.add_argument('dims', metavar='d', type=int, nargs='+',
47+
help='Dimensions')
48+
args = parser.parse_args()
49+
print('Running with args = {}'.format(args))
50+
# parse args
51+
action = Action([ScalarKineticMassTerm(args.m2)])
52+
if args.lam != 0.0:
53+
action.terms.append(ScalarPhi4Term(args.lam))
54+
print('Using action = {}'.format(action))
55+
if args.seed is None:
56+
args.seed = np.random.randint(np.iinfo('uint32').max)
57+
print("Generated random seed = {}".format(args.seed))
58+
np.random.seed(args.seed)
59+
print("Using seed = {}.".format(args.seed))
60+
if args.seed_file is not None:
61+
print("Loading seed cfg from {}".format(args.seed_file))
62+
dtype = np.complex128 if args.complex_type else np.float64
63+
phi = np.fromfile(
64+
args.seed_file, dtype=dtype, count=np.prod(args.dims))
65+
phi = phi.reshape(args.dims)
66+
else:
67+
print("Generating hot start cfg.")
68+
phi = 0.3*np.random.normal(size=args.dims)
69+
if args.complex_type:
70+
phi = phi + 1j * 0.3*np.random.normal(size=args.dims)
71+
# run
72+
main(args.out_prefix, phi, action, args.eps, args.n_leap,
73+
args.iters, args.skip, args.therm)
74+
# final logs
75+
print('Again, args = {}'.format(args))
76+

Diff for: pybin/scalar_field_hmc.py

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""
2+
HMC for (complex) scalar fields in arbitrary dimensions.
3+
"""
4+
5+
import argparse
6+
7+
from scalar_field.scalar_field_hmc import *
8+
9+
10+
def main(out_prefix, init_cfg, action, eps, n_leap, iters, skip, therm):
11+
ensemble, actions = hmc_ensemble(init_cfg, action, eps, n_leap, iters, skip, therm)
12+
print('Ensemble shape = {}.'.format(np.array(ensemble).shape))
13+
fname = out_prefix+'.npy'
14+
print('Writing ensemble to {}.'.format(fname))
15+
np.save(fname, np.array(ensemble))
16+
fname = out_prefix+'.S.npy'
17+
print('Writing actions to {}.'.format(fname))
18+
np.save(fname, np.array(actions))
19+
20+
if __name__ == "__main__":
21+
parser = argparse.ArgumentParser(description="HMC ensemble for scalar field.")
22+
# action
23+
parser.add_argument('--m2', type=float, required=True,
24+
help='Lattice M^2 setting inverse spacing')
25+
parser.add_argument('--lam', type=float, default=0.0,
26+
help='Coeffient lambda of phi^4 term')
27+
parser.add_argument('--complex_type', action='store_true',
28+
help='Use complex phi fields')
29+
# logistics
30+
parser.add_argument('--out_prefix', type=str, required=True,
31+
help='Output prefix for ensemble')
32+
parser.add_argument('--eps', type=float, required=True,
33+
help='HMC traj eps')
34+
parser.add_argument('--n_leap', type=int, required=True,
35+
help='HMC traj leaps')
36+
parser.add_argument('--iters', type=int, required=True,
37+
help='Number of measurements')
38+
parser.add_argument('--therm', type=int, required=True,
39+
help='Number of therm iters')
40+
parser.add_argument('--skip', type=int, required=True,
41+
help='Number skipped between meas')
42+
parser.add_argument('--seed', type=int)
43+
parser.add_argument('--seed_file', type=str)
44+
# lattice
45+
parser.add_argument('dims', metavar='d', type=int, nargs='+',
46+
help='Dimensions')
47+
args = parser.parse_args()
48+
print('Running with args = {}'.format(args))
49+
# parse args
50+
action = Action([ScalarKineticMassTerm(args.m2)])
51+
if args.lam != 0.0:
52+
action.terms.append(ScalarPhi4Term(args.lam))
53+
print('Using action = {}'.format(action))
54+
if args.seed is None:
55+
args.seed = np.random.randint(np.iinfo('uint32').max)
56+
print("Generated random seed = {}".format(args.seed))
57+
np.random.seed(args.seed)
58+
print("Using seed = {}.".format(args.seed))
59+
if args.seed_file is not None:
60+
print("Loading seed cfg from {}".format(args.seed_file))
61+
dtype = np.complex128 if args.complex_type else np.float64
62+
phi = np.fromfile(
63+
args.seed_file, dtype=dtype, count=np.prod(args.dims))
64+
phi = phi.reshape(args.dims)
65+
else:
66+
print("Generating hot start cfg.")
67+
phi = 0.3*np.random.normal(size=args.dims)
68+
if args.complex_type:
69+
phi = phi + 1j * 0.3*np.random.normal(size=args.dims)
70+
# run
71+
main(args.out_prefix, phi, action, args.eps, args.n_leap,
72+
args.iters, args.skip, args.therm)
73+
# final logs
74+
print('Again, args = {}'.format(args))
75+

Diff for: pybin/scalar_field_twopt.py

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
Measure two-point function given scalar field ensemble.
3+
"""
4+
5+
from scalar_field.scalar_field import *
6+
7+
import argparse
8+
import numpy as np
9+
import os
10+
import sys
11+
import time
12+
13+
def compute_twopts(cfgs, verbose):
14+
start = time.time()
15+
corrs = []
16+
Nd = len(cfgs.shape[1:])
17+
spatial_axes = tuple(range(0,Nd-1))
18+
for i,c in enumerate(cfgs):
19+
if verbose and i % 10 == 0:
20+
print('Cfg {} / {} ({:.2f}s)'.format(
21+
i+1, cfgs.shape[0], time.time()-start))
22+
corr = np.array(all_corrs(c, xspace, tspace))
23+
# corr = np.sum(np.mean(corr, axis=0), axis=spatial_axes) # mom proj
24+
corr = np.mean(corr, axis=0)
25+
corrs.append(corr)
26+
print('Two-points done. '
27+
'Total time = {:.2f}s.'.format(time.time()-start))
28+
return corrs
29+
30+
if __name__ == "__main__":
31+
parser = argparse.ArgumentParser(
32+
description='Measure two-pt function for scalar field theory')
33+
# ensemble path
34+
parser.add_argument('--path', type=str, help='Path to .npy format ensemble')
35+
parser.add_argument('--tag', type=str, help='Prefix for .dat format ensemble')
36+
parser.add_argument('--Ncfg', type=int)
37+
parser.add_argument('--use_real64', action='store_true')
38+
parser.add_argument('--use_real32', action='store_true')
39+
# corr params
40+
parser.add_argument('--quiet', action='store_true')
41+
parser.add_argument('dims', metavar='d', type=int, nargs='+')
42+
args = parser.parse_args()
43+
print('Running with args = {}'.format(args))
44+
45+
if args.path is not None: # for .npy ensembles
46+
print('Reading ensemble from {}.'.format(args.path))
47+
cfgs = np.load(args.path)
48+
dtype = cfgs.dtype
49+
args.tag = os.path.splitext(args.path)[0]
50+
else: # for (older format) .dat ensembles
51+
assert args.tag is not None
52+
assert args.Ncfg is not None
53+
assert args.dims is not None
54+
if args.use_real32:
55+
fname = args.tag + '.cfgs.dat'
56+
dtype = np.float32
57+
else:
58+
fname = args.tag + '.dat'
59+
dtype = np.float64 if args.use_real64 else np.complex128
60+
print('Reading ensemble from {}.'.format(fname))
61+
cfgs = np.fromfile(fname, dtype=dtype)
62+
cfgs = cfgs.reshape(args.Ncfg, *args.dims)
63+
corrs = compute_twopts(cfgs, verbose=not args.quiet)
64+
twopt = np.array(corrs)
65+
print('twopt shape {}'.format(twopt.shape))
66+
fname = args.tag + '.twopt.dat'
67+
print('Writing two-pts to {}.'.format(fname))
68+
twopt.tofile(fname)
69+

Diff for: scalar_field/__init__.py

Whitespace-only changes.

Diff for: scalar_field/scalar_field.py

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
"""
2+
Library for lattice scalar field functionality.
3+
"""
4+
5+
import itertools
6+
import math
7+
import numpy as np
8+
9+
### UTILITIES
10+
def tup_add(t1, t2):
11+
assert len(t1) == len(t2)
12+
return tuple([t1[i] + t2[i] for i in range(len(t1))])
13+
def tup_sub(t1, t2):
14+
assert len(t1) == len(t2)
15+
return tuple([t1[i] - t2[i] for i in range(len(t1))])
16+
17+
class ActionTerm(object):
18+
def local_action(self, x, phi):
19+
raise NotImplementedError
20+
def action(self, phi):
21+
raise NotImplementedError
22+
def force(self, phi):
23+
raise NotImplementedError
24+
def __str__(self):
25+
raise NotImplementedError
26+
def __repr__(self):
27+
raise NotImplementedError
28+
29+
class Action(object):
30+
def __init__(self, terms):
31+
self.terms = terms
32+
def local_action(self, x, phi):
33+
out = 0.0
34+
for term in self.terms:
35+
out += term.local_action(x, phi)
36+
return out
37+
def action(self, phi, verbose=False):
38+
out = np.zeros(phi.shape, dtype=phi.dtype)
39+
for term in self.terms:
40+
act = term.action(phi)
41+
if verbose: print('Action from {} = {}'.format(term, np.sum(act)))
42+
out += act
43+
return out
44+
def force(self, phi, verbose=False):
45+
out = np.zeros(phi.shape, dtype=phi.dtype)
46+
for term in self.terms:
47+
F = term.force(phi)
48+
if verbose: print('Force from {} = {:.8g}'.format(term, np.linalg.norm(F)))
49+
out += F
50+
return out
51+
def __str__(self):
52+
return str(self.terms)
53+
__repr__ = __str__
54+
55+
continuum_param = True
56+
57+
class ScalarKineticMassTerm(ActionTerm):
58+
def __init__(self, m2):
59+
self.m2 = m2
60+
def local_action(self, x, phi):
61+
dims = phi.shape
62+
Nd = len(dims)
63+
coords = [x]
64+
for mu in range(Nd):
65+
muhat = [0]*Nd
66+
muhat[mu] = 1
67+
coords.append(tup_add(x, muhat))
68+
coords.append(tup_sub(x, muhat))
69+
coords_arrs = tuple([[] for mu in range(Nd)])
70+
for tup in coords:
71+
for mu in range(Nd):
72+
coords_arrs[mu].append(tup[mu])
73+
lin_inds = np.ravel_multi_index(coords_arrs, dims, mode='wrap')
74+
SK = 0.0
75+
diag = phi[x]
76+
for i in range(Nd):
77+
fwd_coord = list(x)
78+
fwd_coord[i] = (fwd_coord[i] + 1) % dims[i]
79+
bwd_coord = list(x)
80+
bwd_coord[i] = (bwd_coord[i] - 1) % dims[i]
81+
fwd = phi[tuple(fwd_coord)]
82+
bwd = phi[tuple(bwd_coord)]
83+
SK += 2*np.conj(diag) * (diag - fwd - bwd) # factor of 2?
84+
SK = np.real(SK)
85+
SM = self.m2 * np.abs(diag)**2
86+
pre = 0.5 if continuum_param else 1.0
87+
return pre * (SK + SM)
88+
def action(self, phi):
89+
SK = np.zeros(phi.shape, dtype=np.float64)
90+
for mu in range(len(phi.shape)):
91+
fwd = np.roll(phi, -1, axis=mu)
92+
bwd = np.roll(phi, 1, axis=mu)
93+
SK += np.real(np.conj(phi) * (2*phi - fwd - bwd))
94+
SM = self.m2 * np.abs(phi)**2
95+
pre = 0.5 if continuum_param else 1.0
96+
return pre * (SK + SM)
97+
def force(self, phi):
98+
Nd = len(phi.shape)
99+
out = 2 * self.m2 * np.conj(phi)
100+
for mu in range(Nd):
101+
out += 2 * (
102+
2*np.conj(phi)
103+
- np.conj(np.roll(phi, 1, axis=mu))
104+
- np.conj(np.roll(phi, -1, axis=mu)))
105+
# F = -dS/dphi
106+
pre = 0.5 if continuum_param else 1.0
107+
return -np.conj(out) * pre
108+
def __str__(self):
109+
return "KineticMassTerm(M2={:.4f})".format(self.m2)
110+
__repr__ = __str__
111+
112+
class ScalarPhi4Term(ActionTerm):
113+
def __init__(self, lam):
114+
self.lam = lam
115+
def local_action(self, x, phi):
116+
return self.lam * np.abs(phi[x])**4
117+
def action(self, phi):
118+
return self.lam * np.abs(phi)**4
119+
def force(self, phi):
120+
return -np.conj(4 * self.lam * np.conj(phi) * np.abs(phi)**2)
121+
def __str__(self):
122+
return "Phi4Term(lambda={:.8f})".format(self.lam)
123+
__repr__ = __str__
124+
125+
def wrap(p):
126+
return (p + math.pi) % (2*math.pi) - math.pi
127+
128+
def make_smear_corr(smear_x, smear_t):
129+
def smear_corr(corr):
130+
L = corr.shape
131+
Nd = len(L)
132+
corr_fft = np.fft.fftn(corr)
133+
ks = [ np.linspace(0, 2*math.pi, num=L_mu, endpoint=False)
134+
for L_mu in L ]
135+
weights = []
136+
for mu in range(Nd):
137+
assert(L[mu] % 2 == 0)
138+
ks[mu] = (ks[mu] + math.pi) % (2*math.pi) - math.pi
139+
radius = smear_t if mu == Nd-1 else smear_x
140+
weights.append(np.exp(-radius**2 * np.square(ks[mu]) / Nd))
141+
ein_inds = ",".join(map(chr, range(ord('a'), ord('a') + Nd)))
142+
weights = np.einsum(ein_inds, *weights) # big outer product
143+
assert(corr_fft.shape == weights.shape)
144+
corr_fft *= weights
145+
return np.fft.ifftn(corr_fft)
146+
return smear_corr
147+
148+
def all_corrs(phi, xspace, tspace):
149+
corrs = []
150+
coord_ranges = []
151+
for Lx in phi.shape[:-1]:
152+
assert(Lx % xspace == 0)
153+
coord_ranges.append(range(0, Lx, xspace))
154+
assert(phi.shape[-1] % tspace == 0)
155+
coord_ranges.append(range(0, phi.shape[-1], tspace))
156+
all_axes = tuple(range(len(phi.shape)))
157+
for src in itertools.product(*coord_ranges):
158+
shift = tuple(-np.array(src))
159+
corr = np.conj(phi[src]) * np.roll(phi, shift, axis=all_axes)
160+
corrs.append(corr)
161+
return np.array(corrs)

0 commit comments

Comments
 (0)