def random_positions(n): """Returns n random 3-d vectors in a numpy array (n,3). d_min, d_max, npos should be in global scope""" import numpy as np from maths_module import random_vector import sys r = np.zeros((n, 3), dtype=np.float_) # atom 0 is always at the origin, now place the others randomly for i in range(1, r.shape[0]): for pos_try in range(npos): zeta = np.random.rand() d = d_min + (d_max - d_min) * zeta # Magnitude of r r[i, :] = random_vector() * d # In random direction ok = True for j in range(1, i): # Check intermediate atoms if any d = np.sqrt(np.sum((r[i, :] - r[j, :])**2)) ok = ok and (d >= d_min) and (d <= d_max) if ok: break else: print('Exceeded maximum number of tries in random_positions') sys.exit() return r
def random_orientations(n): """Returns n random 3-d vectors in a numpy array (n,3).""" import numpy as np from maths_module import random_vector e = np.empty((n, 3), dtype=np.float_) for i in range(e.shape[0]): e[i, :] = random_vector() return e
def chain_positions(n, bond, soft): """Chooses chain positions randomly, at desired bond length, avoiding overlap.""" import numpy as np from maths_module import random_vector tol = 1.0e-9 iter_max = 500 print("{:40}{:15.6f}".format('Chain, randomly oriented bonds = ', bond)) r = np.empty((n, 3), dtype=np.float_) r[0, :] = [0.0, 0.0, 0.0] # First atom at origin r[1, :] = bond * random_vector( ) # Second atom at random position (bond length away) for i in range(2, n): # Loop over atom indices iter = 0 while True: # Loop until non-overlapping position found r[i, :] = r[i - 1, :] + bond * random_vector( ) # Subsequent atoms randomly placed (bond length away) if soft: # No overlap test break # Overlap test on all so far except bonded neighbour if not chain_overlap(r[i, :], r[:i - 1, :]): break iter = iter + 1 assert iter <= iter_max, 'Too many iterations' r_cm = np.sum(r, axis=0) / n # Compute centre of mass r = r - r_cm # Shift centre of mass to origin for i in range(n - 1): diff_sq = np.sum((r[i, :] - r[i + 1, :])**2) - bond**2 if np.fabs(diff_sq) > tol: print("{}{:5d}{:5d}{:15.8f}".format('Bond length warning', i, i + 1, diff_sq)) return r
def ran_positions(n, box, length, soft, quaternions): """Places atoms at random positions.""" import numpy as np from maths_module import random_quaternion, random_vector # Unlikely to be useful, unless the interaction potential is soft # or the density rather low # For atoms, for which length=0.0, the e-coordinates will be ignored iter_max = 10000 # Max random placement iterations print('Random positions') r = np.empty((n, 3), dtype=np.float_) if quaternions: e = np.empty((n, 4), dtype=np.float_) else: e = np.empty((n, 3), dtype=np.float_) for i in range(n): iter = 0 while True: # Loop until non-overlapping position found r[i, :] = (np.random.rand(3) - 0.5) * box # In range -box/2..box/2 if quaternions: e[i, :] = random_quaternion() else: e[i, :] = random_vector() if soft: break if not overlap(r[i, :], e[i, :], r[:i, :], e[:i, :], box, length): break iter = iter + 1 assert iter <= iter_max, "Too many iterations" return r, e
"quad1_mag"] # Quadrupole moment of molecule 1 quad2_mag = nml["quad2_mag"] if "quad2_mag" in nml else defaults[ "quad2_mag"] # Quadrupole moment of molecule 2 # Write out parameters print("{:40}{:15.6f}".format('Min separation d_min', d_min)) print("{:40}{:15.6f}".format('Max separation d_max', d_max)) print("{:40}{:15.6f}".format('Dipole moment of molecule 1', mu1_mag)) print("{:40}{:15.6f}".format('Dipole moment of molecule 2', mu2_mag)) print("{:40}{:15.6f}".format('Quadrupole moment of molecule 1', quad1_mag)) print("{:40}{:15.6f}".format('Quadrupole moment of molecule 2', quad2_mag)) np.random.seed() # Choose orientations at random e1 = random_vector() e2 = random_vector() # Place atom 2 at origin and atom 1 in a random direction within desired distance range r12_hat = random_vector() r12_mag = np.random.rand() r12_mag = d_min + (d_max - d_min) * r12_mag # Magnitude of r12 r12 = r12_hat * r12_mag # Within desired range of origin c1 = np.dot(e1, r12_hat) # Cosine of angle between e1 and r12 c2 = np.dot(e2, r12_hat) # Cosine of angle between e2 and r12 c12 = np.dot(e1, e2) # Cosine of angle between e1 and e2 print("{:40}{:10.6f}{:10.6f}{:10.6f}".format('Displacement r12', *r12)) print("{:40}{:10.6f}{:10.6f}{:10.6f}".format('Orientation e1', *e1)) print("{:40}{:10.6f}{:10.6f}{:10.6f}".format('Orientation e2', *e2))
def regrow(temperature, m_max, k_max, bond, k_spring, r): """Carries out single regrowth move, returning new r and indicator of success.""" # A short sequence of m atoms (m<=m_max) is deleted and regrown in the CBMC manner # We randomly select which end of the chain to apply each of these operations to # At each stage, k_max different atom positions are tried # Function random_bond selects bond lengths according to the internal (harmonic) potential # Rosenbluth weights are computed using the external (nonbonded) potential # Acceptance/rejection is determined using these weights # r_old and r_new are used as working arrays import numpy as np from maths_module import random_vector w_tol = 1.e-10 # Min weight tolerance n, d = r.shape assert d == 3, 'Dimension error in regrow' r_try = np.empty((k_max, 3), dtype=np.float_) w = np.empty(k_max, dtype=np.float_) std = np.sqrt(temperature / k_spring) # Spring bond standard deviation d_max = 3.0 * std # Impose a limit on variation, say 3*std assert d_max < 0.75 * bond, 'Spring bond strength error' d_max = d_max + bond # This is the actual max d allowed r_old = np.copy(r) # Store copy of r m = 1 + np.random.randint(m_max) # Number of atoms to regrow c = np.random.randint(4) # Growth option # PART 1: CONSTRUCT NEW CONFIGURATION WITH NEW WEIGHT if c == 0: # Remove from end and add to end r[:n - m, :] = r_old[:n - m, :] # Copy first n-m atoms elif c == 1: # Remove from end and add to start r[:n - m, :] = r_old[n - m - 1::-1, :] # Copy and reverse first n-m atoms elif c == 2: # Remove from start and add to start r[:n - m, :] = r_old[:m - 1:-1, :] # Copy and reverse last n-m atoms else: # Remove from start and add to end r[:n - m, :] = r_old[m:, :] # Copy last n-m atoms # Take the opportunity to place atom 0 at the origin r0 = np.copy(r[0, :]) r[:n - m, :] = r[:n - m, :] - r0 w_new = 1.0 for i in range(n - m, n): # Loop to regrow last m atoms, computing new weight for k in range(k_max): # Loop over k_max tries d = random_bond(bond, std, d_max) # Generate random bond length around d=bond r_try[k, :] = r[i - 1, :] + d * random_vector( ) # Trial position in random direction from i-1 partial = potential_1( r_try[k, :], r[:i - 1, :]) # Nonbonded interactions with earlier atoms (not i-1) w[k] = 0.0 if partial.ovr else np.exp( -partial.pot / temperature) # Weight for this try w_sum = np.sum(w) if w_sum < w_tol: # Early exit if this happens at any stage return r_old, False w = w / w_sum k = np.random.choice(k_max, p=w) # Pick winning try according to weights r[i, :] = r_try[k, :] # Store winning position w_new = w_new * w_sum # Accumulate total weight if w_new < w_tol: # Exit if this happens return r_old, False r_new = np.copy(r) # Store new configuration # END OF PART 1: NEW CONFIGURATION AND WEIGHT ARE COMPLETE # PART 2: RECONSTRUCT OLD CONFIGURATION WITH OLD WEIGHT if c == 0 or c == 1: # Remove and hence reconstruct at end r[:, :] = r_old[:, :] # Copy all n atoms else: # Remove and reconstruct at start r[:, :] = r_old[::-1, :] # Copy and reverse all n atoms w_old = 1.0 for i in range(n - m, n): # Old position and weight are stored as try 0 r_try[0, :] = r[i, :] partial = potential_1( r_try[0, :], r[:i - 1, :]) # Nonbonded energy with earlier atoms (not i-1) w[0] = 0.0 if partial.ovr else np.exp( -partial.pot / temperature) # Weight for this try # Remaining tries only required to compute weight for k in range(1, k_max): # Loop over k_max-1 other tries d = random_bond(bond, std, d_max) # Generate random bond length around d=bond r_try[k, :] = r[i - 1, :] + d * random_vector( ) # Trial position in random direction from i-1 partial = potential_1( r_try[k, :], r[:i - 1, :]) # Nonbonded interactions with earlier atoms (not i-1) w[k] = 0.0 if partial.ovr else np.exp( -partial.pot / temperature) # Weight for this try w_sum = np.sum(w) r[i, :] = r_try[ 0, :] # Restore winning position (always the original one) w_old = w_old * w_sum # Accumulate total weight assert w_old > w_tol, 'Old weight error' # END OF PART 2: OLD CONFIGURATION AND WEIGHT ARE COMPLETE # Choose either old or new configuration according to weight # All non-bonded Boltzmann factors are incorporated into the weights # All spring-bond Boltzmann factors are included in the selection of d zeta = np.random.rand() if zeta < (w_new / w_old): return r_new, True else: return r_old, False
def regrow(s, m_max, k_max, bond, q_range, r, q): """Carries out single regrowth move, returning new r, q and indicator of success.""" # A short sequence of m atoms (m<=m_max) is deleted and regrown in the CBMC manner # We randomly select which end of the chain to apply each of these operations to # At each stage, k_max different atom positions are tried # The bond length is fixed throughout # Weights used in the regrowth are athermal, computed only on the basis of the # hard-core overlap part of the non-bonded interactions: essentially they count non-overlaps # Hence they are suitable for use in both NVT and Wang-Landau simulations # r_old and r_new are used as working arrays import numpy as np from maths_module import random_vector w_tol = 1.e-10 # Min weight tolerance n, d = r.shape assert d == 3, 'Dimension error in regrow' r_try = np.empty((k_max, 3), dtype=np.float_) w = np.empty(k_max, dtype=np.float_) r_old = np.copy(r) # Store copy of r q_old = q # Store old q if m_max <= 0: return r_old, q_old, False m = 1 + np.random.randint(m_max) # Number of atoms to regrow c = np.random.randint(4) # Growth option # PART 1: CONSTRUCT NEW CONFIGURATION WITH NEW WEIGHT if c == 0: # Remove from end and add to end r[:n - m, :] = r_old[:n - m, :] # Copy first n-m atoms elif c == 1: # Remove from end and add to start r[:n - m, :] = r_old[n - m - 1::-1, :] # Copy and reverse first n-m atoms elif c == 2: # Remove from start and add to start r[:n - m, :] = r_old[:m - 1:-1, :] # Copy and reverse last n-m atoms else: # Remove from start and add to end r[:n - m, :] = r_old[m:, :] # Copy last n-m atoms # Take the opportunity to place atom 0 at the origin r0 = np.copy(r[0, :]) r[:n - m, :] = r[:n - m, :] - r0 w_new = np.float_(1.0) for i in range(n - m, n): # Loop to regrow last m atoms, computing new weight for k in range(k_max): # Loop over k_max tries r_try[k, :] = r[i - 1, :] + bond * random_vector( ) # Trial position in random direction from i-1 w[k] = weight_1(r_try[k, :], r[:i - 1, :]) # Store overlap weight for this try w_sum = np.sum(w) if w_sum < w_tol: # Early exit if this happens at any stage return r_old, q_old, False w = w / w_sum k = np.random.choice(k_max, p=w) # Pick winning try according to weights r[i, :] = r_try[k, :] # Store winning position w_new = w_new * w_sum # Accumulate total weight if w_new < w_tol: # Exit if this happens return r_old, q_old, False q_new = qcount(r, q_range) # Compute full new nonbonded energy r_new = np.copy(r) # Store new configuration # END OF PART 1: NEW CONFIGURATION AND WEIGHT ARE COMPLETE # PART 2: RECONSTRUCT OLD CONFIGURATION WITH OLD WEIGHT if c == 0 or c == 1: # Remove and hence reconstruct at end r[:, :] = r_old[:, :] # Copy all n atoms else: # Remove and reconstruct at start r[:, :] = r_old[::-1, :] # Copy and reverse all n atoms w_old = np.float_(1.0) for i in range(n - m, n): # Old position and weight are stored as try 0 r_try[0, :] = r[i, :] w[0] = 1 # Current weight must be 1 # Remaining tries only required to compute weight for k in range(1, k_max): # Loop over k_max-1 other tries r_try[k, :] = r[i - 1, :] + bond * random_vector( ) # Trial position in random direction from i-1 w[k] = weight_1(r_try[k, :], r[:i - 1, :]) # Store overlap weight for this try w_sum = np.sum(w) r[i, :] = r_try[ 0, :] # Restore winning position (always the original one) w_old = w_old * w_sum # Accumulate total weight assert w_old > w_tol, 'Old weight error' # END OF PART 2: OLD CONFIGURATION AND WEIGHT ARE COMPLETE # Choose either old or new configuration if accept(s, q_old, q_new, w_old, w_new): return r_new, q_new, True else: return r_old, q_old, False
def pivot(s, phi_max, q_range, r, q): """Carries out a pivot move, returning new r, q and indicator of success.""" # An atom is picked at random, and the part of the chain lying to one side of it # is rotated as a whole, by a random angle, about a randomly oriented axis # There are no weights to take into account in the acceptance/rejection decision # (the function weight_1 is used simply to indicate overlap / no overlap) # r_old and r_new are used as working arrays import numpy as np from maths_module import random_vector, rotate_vector n, d = r.shape assert d == 3, 'Dimension error in regrow' if n < 3: return r, q, False # Makes no sense to pivot such a short chain r_old = np.copy(r) # Store copy of r q_old = q # Store old q c = np.random.randint(2) # Which part to pivot (actually redundant here) if c == 0: # Copy atoms r[:, :] = r_old[:, :] else: # Copy and reverse atoms r[:, :] = r_old[::-1, :] # Take the opportunity to place atom 0 at the origin r0 = np.copy(r[0, :]) r = r - r0 j = np.random.randint(1, n - 1) # Pivot position (not at either end) rj = r[j, :] # Pivot point for i in range(1, j + 1): # Loop over static atoms, redundant but for roundoff if weight_1(r[i, :], r[:i - 1, :]) == 0: # Check for overlaps return r_old, q_old, False # Pivot, and check for overlap in new configuration # NB include overlap checks within rotated segment, because of roundoff axis = random_vector() # Pivot rotation axis phi = phi_max * (2.0 * np.random.rand() - 1.0 ) # Pivot rotation angle in desired range for i in range(j + 1, n): # Loop over moving atoms rij = r[i, :] - rj # Relative vector of atom rij = rotate_vector(phi, axis, rij) # Rotate relative vector r[i, :] = rj + rij # New absolute position if weight_1(r[i, :], r[:i - 1, :]) == 0: # Check for overlaps return r_old, q_old, False q_new = qcount(r, q_range) # Compute full new nonbonded energy r_new = np.copy(r) # Store new configuration # Choose either old or new configuration if accept(s, q_old, q_new): return r_new, q_new, True else: return r_old, q_old, False