Coverage for gamdpy/interactions/tether.py: 19%
63 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
2import numba
3import numpy as np
4from numba import cuda
6from .make_fixed_interactions import make_fixed_interactions # tether is an example of 'fixed' interactions
8# Abstract Base Class and type annotation
9from .interaction import Interaction
10from gamdpy import Configuration
13class Tether(Interaction):
14 """ Connect particles to anchor-points in space with a harmonic spring force.
16 Parameters
17 ----------
18 Points and spring constants are defined using either
19 (a) A list of particle indices to be tethered and associated list of spring constants
20 (b) A list of particle types to be tethered and associated list of spring constants
22 See examples/tethered_particles.py
23 """
25 def __init__(self):
26 self.anchor_points_set = False
28 def set_anchor_points_from_lists(self, particle_indices, spring_constants, configuration):
29 """ Set anchor points and spring constants for tethered particles.
31 Parameters
32 ----------
34 particle_indices : list of int
35 List of particle indices to be tethered.
37 spring_constants : list of float
38 List of spring constants.
40 configuration : rumd.Configuration
41 Configuration object containing particle thether positions.
43 """
45 nsprings, nparticles = len(spring_constants), len(particle_indices)
47 if nsprings != nparticles:
48 raise ValueError("Each particle must have exactly one spring connection - array must be same length");
50 indices, tether_params = [], []
51 for n in range(nparticles):
52 indices.append([n, particle_indices[n]])
53 pos = configuration['r'][particle_indices[n]]
54 tether_params.append( [pos[0], pos[1], pos[2], spring_constants[n]] )
56 self.tether_params = np.array(tether_params, dtype=np.float32)
57 self.indices = np.array(indices, dtype=np.int32)
59 self.anchor_points_set = True
62 def set_anchor_points_from_types(self, particle_types, spring_constants, configuration):
64 nsprings, ntypes = len(spring_constants), len(particle_types)
66 if ntypes != nsprings:
67 raise ValueError("Each type must have exactly one spring connection - arrays must be same length")
69 indices, tether_params, counter = [], [], 0
70 for n in range(configuration.N):
71 for m in range(ntypes):
72 if configuration.ptype[n]==particle_types[m]:
73 indices.append([counter, n])
74 pos = configuration['r'][n]
75 tether_params.append( [pos[0], pos[1], pos[2], spring_constants[m]] )
76 counter = counter + 1
77 break
79 self.tether_params = np.array(tether_params, dtype=np.float32)
80 self.indices = np.array(indices, dtype=np.int32)
82 self.anchor_points_set = True
84 def get_params(self, configuration: Configuration, compute_plan: dict, verbose=False) -> tuple:
85 if self.anchor_points_set == False:
86 raise ValueError("Anchor points not defined")
88 self.d_pindices = cuda.to_device(self.indices)
89 self.d_tether_params = cuda.to_device(self.tether_params);
91 return (self.d_pindices, self.d_tether_params)
93 def get_kernel(self, configuration: Configuration, compute_plan: dict, compute_flags: dict[str,bool], verbose=False):
94 # Unpack parameters from configuration and compute_plan
95 D, N = configuration.D, configuration.N
96 pb, tp, gridsync, UtilizeNIII = [compute_plan[key] for key in ['pb', 'tp', 'gridsync', 'UtilizeNIII']]
97 num_blocks = (N - 1) // pb + 1
99 compute_u = compute_flags['U']
100 # Note w, lap, stresses not relevant here
102 r_id, f_id = [configuration.vectors.indices[key] for key in ['r', 'f']]
104 if compute_u:
105 u_id = configuration.sid['U']
107 dist_sq_dr_function = numba.njit(configuration.simbox.get_dist_sq_dr_function())
109 def tether_calculator(vectors, scalars, ptype, sim_box, indices, values):
111 dr = cuda.local.array(shape=D,dtype=numba.float32)
112 dist_sq = dist_sq_dr_function(values[indices[0]][:D], vectors[r_id][indices[1]], sim_box, dr)
114 spring = values[indices[0]][3]
116 f=vectors[f_id][indices[1]];
118 for k in range(D):
119 f[k] = f[k] + dr[k]*spring
121 Epot = numba.float32(0.5)*spring*dist_sq
122 # if compute_u:
123 # What's going on here? Why the atomic add?
124 cuda.atomic.add(scalars, (indices[0], u_id), Epot)
126 return
130 return make_fixed_interactions(configuration, tether_calculator, compute_plan, verbose=False)