def __init__(self, x0, dynamics, potential, rng, **kwargs): super(Hybrid_Monte_Carlo, self).__init__() self.initArgs(locals()) self.defaults = { 'accept_kwargs': { # kwargs to pass to accept 'store_acceptance': False } } self.initDefaults(kwargs) # legacy support - need to update a = 'store_acceptance' if a in kwargs: self.accept_kwargs[a] = kwargs[a] self.momentum = Momentum(self.rng) self.accept = Accept_Reject(self.rng, **self.accept_kwargs) # Take the position in just for the shape # as note this is a fullRefresh so the return is # entirely gaussian noise. It is independent of X # so no need to preflip X before using as an input self.p0 = self.momentum.fullRefresh(self.x0) # intial mom. sample shapes = (self.x0.shape, self.p0.shape) checks.tryAssertEqual(*shapes, error_msg=' x0.shape != p0.shape' \ +'\n x0: {}, p0: {}'.format(*shapes)) self.h_old = None pass
def kineticEnergy(self, p): """n-dim KE Required Inputs p :: np.matrix (col vector) :: momentum vector """ checks.tryAssertEqual( len(p.shape), 2, ' expected momentum dims = 2.\n> p: {}'.format(p)) return .5 * (p**2).sum(axis=0)
def gradSquared(lattice, position, a_power=0): """lattice gradient^2 for a point with a periodic boundary The gradient is squared to be symmetric and avoid non differentiability in the continuum limit as a^2 -> 0 -- See Feynman & Hibbs: Quantum Mechanics and Paths Integrals pg. 179 Required Inputs lattice :: Periodic_Lattice :: an overloaded numpy array position :: (integer,) :: determines the position of the array Optional Inputs a_power :: integer :: divide by (lattice spacing)^a_power Expectations position is a tuple that gives current point in the n-dim lattice """ # check that a lattice is input checks.tryAssertEqual( type(lattice), Periodic_Lattice, "mismatch of lattice type...\ntype received: {}\ntype expected: {}". format(type(lattice), Periodic_Lattice)) # as in laplacian() checks.tryAssertEqual( len(position), lattice.lattice_dim, "mismatch of dims...\ndim received: {}\ndim expected: {}".format( len(position), lattice.lattice_dim)) # ___this is really cool___ # firstly: realise that we want True on each case # where the index == the given axis to shift # secondly: fancy indexing will index the matrix # for each row in the ndarray provided # so this: # forwards = np.empty(lattice.lattice_dim) # all_dims = range(len(position)) # for axis in all_dims: # iterate through axes (lattice dimensions) # mask = np.in1d(all_dims, axis) # boolean mask. True on the index = axis # plus = position + mask # lattice shift operator # forwards[axis] = lattice[plus] - lattice[pos] # forwards difference # is equivalent to: plus = position + np.identity(lattice.lattice_dim, dtype=int) # repeats the position across nxn matrix repeated_pos = np.asarray((position, ) * lattice.lattice_dim) forwards = lattice[plus] - lattice[repeated_pos] # forwards difference # ___end cool section___ grad = (forwards**2).sum() # lattice spacing if a_power: grad = grad / 1.0**a_power return grad
def potentialEnergy(self, x): """n-dim potential Required Inputs x :: np.matrix (col vector) :: position vector """ checks.tryAssertEqual( x.shape, self.mean.shape, ' expected x.shape = self.mean.shape\n> x: {}, mu: {}'.format( x.shape, self.mean.shape)) x -= self.mean return .5 * (np.dot(x.T, self.cov_inv) * x).sum(axis=0)
def gradPotentialEnergy(self, x): """n-dim gradient Notes discard just stores extra arguments passed for compatibility with the lattice versions """ checks.tryAssertEqual( len(x.shape), 2, ' expected position dims = 2.\n> x: {}'.format(x)) # this is constant irrelevent of the index return np.dot(self.cov_inv, x)
def hamiltonian(self, p, x): """Returns the Hamiltonian Required Inputs p :: np.array (nd) :: momentum array x :: class :: see lattice.py for info """ if not hasattr(self, 'debug'): self.debug = False if self.debug: h = self.kE(p) + self.uE(x)[0] else: h = self.kE(p) + self.uE(x) # check 1 dimensional checks.tryAssertEqual( h.shape, (1, ) * len(h.shape), ' hamiltonian() not scalar.\n> shape: {}'.format(h.shape)) return h.reshape(1)
def laplacian(lattice, position, a_power=0): """lattice Laplacian for a point with a periodic boundary Required Inputs lattice :: Periodic_Lattice :: an overloaded numpy array position :: (integer,) :: determines the position of the array Optional Inputs a_power :: integer :: divide by (lattice spacing)^a_power Expectations position is a tuple that gives current point in the n-dim lattice """ # check that a lattice is input checks.tryAssertEqual( type(lattice), Periodic_Lattice, "mismatch of lattice type...\ntype received: {}\ntype expected: {}". format(type(lattice), Periodic_Lattice)) # check that the tuple recieved is the same length as the # shape of the target array: Should do gradient over all dims # gradient should be an array of the length of degrees of freedom checks.tryAssertEqual( len(position), lattice.lattice_dim, "mismatch of dims...\ndim received: {}\ndim expected: {}".format( len(position), lattice.lattice_dim)) # ___this is really cool___ # see gradSquared for explanation plus = position + np.identity(lattice.lattice_dim, dtype=int) minus = position - np.identity(lattice.lattice_dim, dtype=int) # repeats the position across nxn matrix repeated_pos = np.asarray((position, ) * lattice.lattice_dim) lap = lattice[plus] - 2. * lattice[repeated_pos] + lattice[minus] # ___end cool section___ # euclidean space so trivial metric lap = lap.sum() # multiply by approciate power of lattice spacing if a_power: lap = lap / lattice.lattice_spacing**a_power return lap
def potentialEnergy(self, positions): """n-dim potential This is the action. In HMC, the action is the potential in the shadow hamiltonian. Here the laplacian in the action is used with 1/a the potential is then Va Required Inputs positions :: class :: see lattice.py for info """ lattice = positions # shortcut for brevity x_sq_sum = (lattice**2).ravel().sum() v_sq_sum = np.array(0.) # initiate velocity squared # sum (integrate) across euclidean-space (i.e. all lattice sites) for idx in np.ndindex(lattice.shape): # sum velocity squared v_sq = gradSquared(positions, idx, a_power=1) # gradient should be an array of the length of degrees of freedom checks.tryAssertEqual(v_sq.shape, (), ' derivative^2 shape should be scalar' \ + '\n> v_sq shape: {}'.format(v_sq_sum.shape) ) # sum to previous v_sq_sum += v_sq #### free action S_0: m/2 \phi(v^2 + m)\phi kinetic = .5 * self.m0 * v_sq_sum u_0 = .5 * self.mu**2 * x_sq_sum ### End free action # Add interation terms if required if self.phi_3: # phi^3 term x_3_sum = (lattice**3).sum() u_3 = self.phi_3 * x_3_sum / np.math.factorial(3) else: u_3 = 0. if self.phi_4: # phi^4 term x_4_sum = (lattice**4).sum() u_4 = self.phi_4 * x_4_sum / np.math.factorial(4) else: u_4 = 0. # the potential terms in the action potential = u_0 + u_3 + u_4 # multiply the potential by the lattice spacing as required euclidean_action = kinetic + positions.lattice_spacing * potential if self.debug: # alows for debugging ret_val = [ euclidean_action, kinetic, potential * positions.lattice_spacing ] else: ret_val = euclidean_action return ret_val