Coverage for gamdpy/interactions/bonds.py: 12%

102 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-06-14 15:25 +0200

1import numpy as np 

2import numba 

3import math 

4from numba import cuda 

5from .make_fixed_interactions import make_fixed_interactions # bonds is an example of 'fixed' interactions 

6 

7# Abstract Base Class and type annotation 

8from .interaction import Interaction 

9from gamdpy import Configuration 

10 

11 

12class Bonds(Interaction): 

13 """ Fixed bond interactions between particles, such as harmonic bonds or FENE bonds. 

14 

15 Parameters 

16 ---------- 

17 

18 bond_potential : function 

19 A function that takes the distance between two particles and the bond type as arguments and returns the potential energy, force and laplacian. 

20 See :func:gamdpy.potential_functions.harmonic_bond_function for an example. 

21 

22 potential_params : list 

23 A list of parameters for each bond type. Each entry is a list of parameters for a specific bond type. 

24 

25 indices : list 

26 A list of lists, each containing the indices of the two particles involved in a bond and the bond 

27 

28 See Also 

29 -------- 

30 

31 gamdpy.harmonic_bond_function : Harmonic bond potential 

32 

33 """ 

34 def __init__(self, bond_potential, potential_params, indices): 

35 self.bond_potential = bond_potential 

36 self.potential_params = potential_params 

37 self.indices = indices 

38 

39 def get_params(self, configuration: Configuration, compute_plan: dict, verbose=False) -> tuple: 

40 self.N = configuration.N 

41 self.potential_params_array = np.array(self.potential_params, dtype=np.float32) 

42 assert len(self.potential_params_array.shape)==2 # for now... 

43 self.num_types = self.potential_params_array.shape[0] 

44 self.indices_array = np.array(self.indices, dtype=np.int32) 

45 assert self.indices_array.shape[1] == 3 # i, j, bond_type 

46 assert max(self.indices_array[:,-1]) <= self.num_types 

47 

48 if verbose: 

49 print(f'Setting up bond interactions: {self.N} particles,') 

50 print(f'{self.num_types} bond types, {self.indices_array.shape[0]} bonds in total.') 

51 

52 self.d_indices = cuda.to_device(self.indices_array) 

53 self.d_params = cuda.to_device(self.potential_params_array) 

54 return (self.d_indices, self.d_params) 

55 

56 

57 def get_kernel(self, configuration: Configuration, compute_plan: dict, compute_flags: dict[str,bool], verbose=False): 

58 # Unpack parameters from configuration and compute_plan 

59 D, N = configuration.D, configuration.N 

60 pb, tp, gridsync, UtilizeNIII = [compute_plan[key] for key in ['pb', 'tp', 'gridsync', 'UtilizeNIII']] 

61 num_blocks = (N - 1) // pb + 1 

62 

63 compute_u = compute_flags['U'] 

64 compute_w = compute_flags['W'] 

65 compute_lap = compute_flags['lapU'] 

66 compute_stresses = compute_flags['stresses'] 

67 

68 if verbose: 

69 print('get_kernel: Bonds:') 

70 print(f'\tpb: {pb}, tp:{tp}, num_blocks:{num_blocks}') 

71 print(f'\tNumber (virtual) particles: {num_blocks*pb}') 

72 print(f'\tNumber of threads {num_blocks*pb*tp}') 

73 if compute_stresses: 

74 print('\tIncluding computation of stress tensor') 

75 

76 # Unpack indices for vectors and scalars to be compiled into kernel 

77 r_id, f_id = [configuration.vectors.indices[key] for key in ['r', 'f']] 

78 

79 if compute_u: 

80 u_id = configuration.sid['U'] 

81 if compute_w: 

82 w_id = configuration.sid['W'] 

83 if compute_lap: 

84 lap_id = configuration.sid['lapU'] 

85 

86 if compute_stresses: 

87 sx_id = configuration.vectors.indices['sx'] 

88 if D > 1: 

89 sy_id = configuration.vectors.indices['sy'] 

90 if D > 2: 

91 sz_id = configuration.vectors.indices['sz'] 

92 if D > 3: 

93 sw_id = configuration.vectors.indices['sw'] 

94 

95 

96 dist_sq_dr_function = numba.njit(configuration.simbox.get_dist_sq_dr_function()) 

97 bondpotential_function = numba.njit(self.bond_potential) 

98 

99 virial_factor = numba.float32( 0.5/configuration.D) 

100 half = numba.float32(0.5) 

101 

102 def bond_calculator(vectors, scalars, ptype, sim_box, indices, values): 

103 

104 dr = cuda.local.array(shape=D,dtype=numba.float32) 

105 dist_sq = dist_sq_dr_function(vectors[r_id][indices[1]], vectors[r_id][indices[0]], sim_box, dr) 

106 u, s, umm = bondpotential_function(math.sqrt(dist_sq), values[indices[2]]) 

107 

108 for k in range(D): 

109 cuda.atomic.add(vectors, (f_id, indices[0], k), -dr[k]*s) # Force 

110 cuda.atomic.add(vectors, (f_id, indices[1], k), +dr[k]*s) 

111 if compute_w: 

112 cuda.atomic.add(scalars, (indices[0], w_id), dr[k]*dr[k]*s*virial_factor) # Virial 

113 cuda.atomic.add(scalars, (indices[1], w_id), dr[k]*dr[k]*s*virial_factor) 

114 if compute_stresses: 

115 cuda.atomic.add(vectors[sx_id], (indices[0], k), - half * s * dr[0] * dr[k]) 

116 cuda.atomic.add(vectors[sx_id], (indices[1], k), - half * s * dr[0] * dr[k]) 

117 if D > 1: 

118 cuda.atomic.add(vectors[sy_id], (indices[0], k), - half * s * dr[1] * dr[k]) 

119 cuda.atomic.add(vectors[sy_id], (indices[1], k), - half * s * dr[1] * dr[k]) 

120 if D > 2: 

121 cuda.atomic.add(vectors[sz_id], (indices[0], k), - half * s * dr[2] * dr[k]) 

122 cuda.atomic.add(vectors[sz_id], (indices[1], k), - half * s * dr[2] * dr[k]) 

123 if D > 3: 

124 cuda.atomic.add(vectors[sw_id], (indices[0], k), - half * s * dr[3] * dr[k]) 

125 cuda.atomic.add(vectors[sw_id], (indices[1], k), - half * s * dr[3] * dr[k]) 

126 

127 

128 if compute_u: 

129 cuda.atomic.add(scalars, (indices[0], u_id), u*numba.float32(0.5)) # Potential enerrgy 

130 cuda.atomic.add(scalars, (indices[1], u_id), u*numba.float32(0.5)) 

131 if compute_lap: 

132 lap = numba.float32(1-D)*s + umm # Laplacian 

133 cuda.atomic.add(scalars, (indices[0], lap_id), lap) 

134 cuda.atomic.add(scalars, (indices[1], lap_id), lap) 

135 

136 

137 return 

138 

139 return make_fixed_interactions(configuration, bond_calculator, compute_plan, verbose=False) 

140 

141 def get_exclusions(self, configuration, max_number_exclusions=20): 

142 exclusions = np.zeros((configuration.N, max_number_exclusions+1), dtype=np.int32) # last entry: number of actual exclusions 

143 for i, j, _ in self.indices: # bond involving particles 'i' and 'j'. We dont care about the bond type (maybe later) 

144 if exclusions[i,-1] < max_number_exclusions: 

145 exclusions[i,exclusions[i,-1]] = j 

146 exclusions[i,-1] += 1 

147 

148 if exclusions[j,-1] < max_number_exclusions: 

149 exclusions[j,exclusions[j,-1]] = i 

150 exclusions[j,-1] += 1 

151 

152 assert np.max(exclusions[:,-1]) <= max_number_exclusions 

153 return exclusions 

154