Coverage for gamdpy/runtime_actions/trajectory_saver.py: 66%
110 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1import numpy as np
2import numba
3import math
4from numba import cuda, config
6from .runtime_action import RuntimeAction
9class TrajectorySaver(RuntimeAction):
10 """
11 Runtime action for saving configurations during timeblock.
12 Does logarithmic saving.
13 """
15 def __init__(self, include_simbox=False, verbose=False, compression="gzip", compression_opts=4) -> None:
17 self.include_simbox = include_simbox
18 self.num_vectors = 2 # 'r' and 'r_im' (for now!)
19 self.compression = compression
20 if self.compression == 'gzip':
21 self.compression_opts = compression_opts
22 else:
23 self.compression_opts = None
24 #self.sid = {"r":0, "r_im":1}
26 def setup(self, configuration, num_timeblocks: int, steps_per_timeblock: int, output, verbose=False) -> None:
27 self.configuration = configuration
29 if type(num_timeblocks) != int or num_timeblocks < 0:
30 raise ValueError(f'num_timeblocks ({num_timeblocks}) should be non-negative integer.')
31 self.num_timeblocks = num_timeblocks
33 if type(steps_per_timeblock) != int or steps_per_timeblock < 0:
34 raise ValueError(f'steps_per_timeblock ({steps_per_timeblock}) should be non-negative integer.')
35 self.steps_per_timeblock = steps_per_timeblock
37 self.conf_per_block = int(math.log2(self.steps_per_timeblock)) + 2 # Should be user controllable
39 # Setup output
40 if verbose:
41 print(f'Storing results in memory. Expected footprint {self.num_timeblocks * self.conf_per_block * self.num_vectors * self.configuration.N * self.configuration.D * 4 / 1024 / 1024:.2f} MB.')
43 if 'trajectory_saver' in output.keys():
44 del output['trajectory_saver']
45 output.create_group('trajectory_saver')
47 # Compression has a different syntax depending if is gzip or not because gzip can have also a compression_opts
48 # it is possible to use compression=None for not compressing the data
49 output.create_dataset('trajectory_saver/positions',
50 shape=(self.num_timeblocks, self.conf_per_block, self.configuration.N, self.configuration.D),
51 chunks=(1, 1, self.configuration.N, self.configuration.D),
52 dtype=np.float32, compression=self.compression, compression_opts=self.compression_opts)
53 output.create_dataset('trajectory_saver/images',
54 shape=(self.num_timeblocks, self.conf_per_block, self.configuration.N, self.configuration.D),
55 chunks=(1, 1, self.configuration.N, self.configuration.D),
56 dtype=np.int32, compression=self.compression, compression_opts=self.compression_opts)
57 output['trajectory_saver'].attrs['compression_info'] = f"{self.compression} with opts {self.compression_opts}"
59 #output.attrs['vectors_names'] = list(self.sid.keys())
60 if self.include_simbox:
61 if 'sim_box' in output['trajectory_saver'].keys():
62 del output['trajectory_saver/sim_box']
63 output.create_dataset('trajectory_saver/sim_box',
64 shape=(self.num_timeblocks, self.conf_per_block, self.configuration.simbox.len_sim_box_data))
66 flag = config.CUDA_LOW_OCCUPANCY_WARNINGS
67 config.CUDA_LOW_OCCUPANCY_WARNINGS = False
68 self.zero_kernel = self.make_zero_kernel()
69 config.CUDA_LOW_OCCUPANCY_WARNINGS = flag
71 def get_params(self, configuration, compute_plan):
72 self.conf_array = np.zeros((self.conf_per_block, self.num_vectors, self.configuration.N, self.configuration.D),
73 dtype=np.float32)
74 self.d_conf_array = cuda.to_device(self.conf_array)
76 if self.include_simbox:
77 self.sim_box_output_array = np.zeros((self.conf_per_block, self.configuration.simbox.len_sim_box_data), dtype=np.float32)
78 self.d_sim_box_output_array = cuda.to_device(self.sim_box_output_array)
79 return (self.d_conf_array, self.d_sim_box_output_array)
80 else:
81 return (self.d_conf_array,)
83 def make_zero_kernel(self):
84 # Unpack parameters from configuration and compute_plan
85 D, num_part = self.configuration.D, self.configuration.N
86 pb = 32
87 num_blocks = (num_part - 1) // pb + 1
89 def zero_kernel(array):
90 Nx, Ny, Nz, Nw = array.shape
91 global_id = cuda.grid(1)
93 if global_id < Nz: # particles
94 for i in range(Nx):
95 for j in range(Ny):
96 for k in range(Nw):
97 array[i, j, global_id, k] = numba.float32(0.0)
99 zero_kernel = cuda.jit(zero_kernel)
100 return zero_kernel[num_blocks, pb]
102 def update_at_end_of_timeblock(self, timeblock: int, output_reference):
103 data = self.d_conf_array.copy_to_host()
104 # note that d_conf_array has dimensions (self.conf_per_block, 2, self.configuration.N, self.configuration.D)
105 output_reference['trajectory_saver/positions'][timeblock], output_reference['trajectory_saver/images'][timeblock] = data[:, 0], data[:, 1]
106 #output['trajectory_saver'][block, :] = self.d_conf_array.copy_to_host()
107 if self.include_simbox:
108 output_reference['trajectory_saver/sim_box'][timeblock, :] = self.d_sim_box_output_array.copy_to_host()
109 self.zero_kernel(self.d_conf_array)
111 def get_poststep_kernel(self, configuration, compute_plan, verbose=False):
112 pb, tp, gridsync = [compute_plan[key] for key in ['pb', 'tp', 'gridsync']]
113 if gridsync:
114 def kernel(grid, vectors, scalars, r_im, sim_box, step, conf_saver_params):
115 pass
116 return
117 return cuda.jit(device=gridsync)(kernel)
118 else:
119 def kernel(grid, vectors, scalars, r_im, sim_box, step, conf_saver_params):
120 pass
121 return kernel
124 def get_prestep_kernel(self, configuration, compute_plan, verbose=False):
125 # Unpack parameters from configuration and compute_plan
126 D, num_part = configuration.D, configuration.N
127 pb, tp, gridsync = [compute_plan[key] for key in ['pb', 'tp', 'gridsync']]
128 num_blocks = (num_part - 1) // pb + 1
129 sim_box_array_length = configuration.simbox.len_sim_box_data
130 include_simbox = self.include_simbox
132 # Unpack indices for scalars to be compiled into kernel
133 r_id, = [configuration.vectors.indices[key] for key in ['r', ]]
135 def kernel(grid, vectors, scalars, r_im, sim_box, step, conf_saver_params):
136 if include_simbox:
137 conf_array, sim_box_output_array = conf_saver_params
138 else:
139 conf_array, = conf_saver_params
141 Flag = False
142 if step == 0:
143 Flag = True
144 save_index = 0
145 else:
146 b = np.int32(math.log2(np.float32(step)))
147 c = 2 ** b
148 if step == c:
149 Flag = True
150 save_index = b + 1
152 if Flag:
153 global_id, my_t = cuda.grid(2)
154 if global_id < num_part and my_t == 0:
155 for k in range(D):
156 conf_array[save_index, 0, global_id, k] = vectors[r_id][global_id, k]
157 conf_array[save_index, 1, global_id, k] = np.float32(r_im[global_id, k])
158 if include_simbox and global_id == 0:
159 for k in range(sim_box_array_length):
160 sim_box_output_array[save_index, k] = sim_box[k]
161 return
163 kernel = cuda.jit(device=gridsync)(kernel)
165 if gridsync:
166 return kernel # return device function
167 else:
168 return kernel[num_blocks, (pb, 1)] # return kernel, incl. launch parameters