1#![allow(dead_code)]
11
12#[derive(Debug, Clone, Copy, PartialEq)]
16pub struct GpuMdAtom {
17 pub pos: [f32; 3],
19 pub vel: [f32; 3],
21 pub force: [f32; 3],
23 pub mass: f32,
25 pub charge: f32,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq)]
31pub struct GpuMdParams {
32 pub epsilon: f32,
34 pub sigma: f32,
36 pub cutoff: f32,
38 pub box_size: [f32; 3],
40 pub n_atoms: usize,
42}
43
44#[derive(Debug, Clone)]
46pub struct GpuMdBuffer {
47 pub atoms: Vec<GpuMdAtom>,
49 pub step: usize,
51}
52
53impl GpuMdBuffer {
54 pub fn new(n: usize) -> Self {
56 Self {
57 atoms: vec![
58 GpuMdAtom {
59 pos: [0.0; 3],
60 vel: [0.0; 3],
61 force: [0.0; 3],
62 mass: 1.0,
63 charge: 0.0,
64 };
65 n
66 ],
67 step: 0,
68 }
69 }
70
71 pub fn len(&self) -> usize {
73 self.atoms.len()
74 }
75
76 pub fn is_empty(&self) -> bool {
78 self.atoms.is_empty()
79 }
80}
81
82pub fn lj_force_gpu(r: f32, params: &GpuMdParams) -> f32 {
89 if r >= params.cutoff || r < 1e-10 {
90 return 0.0;
91 }
92 let sr = params.sigma / r;
93 let sr6 = sr * sr * sr * sr * sr * sr;
94 let sr12 = sr6 * sr6;
95 4.0 * params.epsilon * (12.0 * sr12 - 6.0 * sr6) / r
97}
98
99pub fn lj_potential_gpu(r: f32, params: &GpuMdParams) -> f32 {
104 if r >= params.cutoff || r < 1e-10 {
105 return 0.0;
106 }
107 let sr = params.sigma / r;
108 let sr6 = sr * sr * sr * sr * sr * sr;
109 let sr12 = sr6 * sr6;
110 4.0 * params.epsilon * (sr12 - sr6)
111}
112
113pub fn pbc_distance_gpu(a: [f32; 3], b: [f32; 3], box_size: [f32; 3]) -> f32 {
119 let mut r2 = 0.0_f32;
120 for k in 0..3 {
121 let mut d = a[k] - b[k];
122 let l = box_size[k];
123 if l > 0.0 {
124 d -= (d / l).round() * l;
125 }
126 r2 += d * d;
127 }
128 r2.sqrt()
129}
130
131fn pbc_displacement_gpu(a: [f32; 3], b: [f32; 3], box_size: [f32; 3]) -> [f32; 3] {
133 let mut disp = [0.0_f32; 3];
134 for k in 0..3 {
135 let mut d = a[k] - b[k];
136 let l = box_size[k];
137 if l > 0.0 {
138 d -= (d / l).round() * l;
139 }
140 disp[k] = d;
141 }
142 disp
143}
144
145pub fn compute_forces_gpu(buf: &mut GpuMdBuffer, params: &GpuMdParams) {
151 let n = buf.atoms.len();
152 for atom in buf.atoms.iter_mut() {
154 atom.force = [0.0; 3];
155 }
156 for i in 0..n {
157 for j in (i + 1)..n {
158 let pi = buf.atoms[i].pos;
159 let pj = buf.atoms[j].pos;
160 let disp = pbc_displacement_gpu(pi, pj, params.box_size);
161 let r2 = disp[0] * disp[0] + disp[1] * disp[1] + disp[2] * disp[2];
162 let r = r2.sqrt();
163 if r < 1e-10 || r >= params.cutoff {
164 continue;
165 }
166 let f_mag = lj_force_gpu(r, params);
167 let scale = -f_mag / r;
170 buf.atoms[i].force[0] += scale * disp[0];
171 buf.atoms[i].force[1] += scale * disp[1];
172 buf.atoms[i].force[2] += scale * disp[2];
173 buf.atoms[j].force[0] -= scale * disp[0];
174 buf.atoms[j].force[1] -= scale * disp[1];
175 buf.atoms[j].force[2] -= scale * disp[2];
176 }
177 }
178}
179
180pub fn verlet_integrate_gpu(buf: &mut GpuMdBuffer, dt: f32) {
188 for atom in buf.atoms.iter_mut() {
189 let inv_mass = if atom.mass > 1e-10 {
190 1.0 / atom.mass
191 } else {
192 0.0
193 };
194 atom.vel[0] += atom.force[0] * inv_mass * dt;
195 atom.vel[1] += atom.force[1] * inv_mass * dt;
196 atom.vel[2] += atom.force[2] * inv_mass * dt;
197 atom.pos[0] += atom.vel[0] * dt;
198 atom.pos[1] += atom.vel[1] * dt;
199 atom.pos[2] += atom.vel[2] * dt;
200 }
201 buf.step += 1;
202}
203
204pub fn kinetic_energy_gpu(buf: &GpuMdBuffer) -> f32 {
208 buf.atoms
209 .iter()
210 .map(|a| 0.5 * a.mass * (a.vel[0] * a.vel[0] + a.vel[1] * a.vel[1] + a.vel[2] * a.vel[2]))
211 .sum()
212}
213
214pub fn potential_energy_gpu(buf: &GpuMdBuffer, params: &GpuMdParams) -> f32 {
216 let n = buf.atoms.len();
217 let mut pe = 0.0_f32;
218 for i in 0..n {
219 for j in (i + 1)..n {
220 let r = pbc_distance_gpu(buf.atoms[i].pos, buf.atoms[j].pos, params.box_size);
221 pe += lj_potential_gpu(r, params);
222 }
223 }
224 pe
225}
226
227pub fn temperature_gpu(buf: &GpuMdBuffer) -> f32 {
232 let n = buf.atoms.len();
233 if n == 0 {
234 return 0.0;
235 }
236 let kb = 8.314e-3_f32; let ke = kinetic_energy_gpu(buf);
238 2.0 * ke / (3.0 * n as f32 * kb)
239}
240
241pub fn rescale_velocities_gpu(buf: &mut GpuMdBuffer, target_temp: f32) {
247 let t_curr = temperature_gpu(buf);
248 if t_curr < 1e-10 || target_temp < 0.0 {
249 return;
250 }
251 let scale = (target_temp / t_curr).sqrt();
252 for atom in buf.atoms.iter_mut() {
253 atom.vel[0] *= scale;
254 atom.vel[1] *= scale;
255 atom.vel[2] *= scale;
256 }
257}
258
259#[cfg(test)]
262mod tests {
263 use super::*;
264
265 fn default_params() -> GpuMdParams {
266 GpuMdParams {
267 epsilon: 1.0,
268 sigma: 1.0,
269 cutoff: 3.5,
270 box_size: [10.0; 3],
271 n_atoms: 4,
272 }
273 }
274
275 fn make_buf_grid(n: usize) -> GpuMdBuffer {
276 let mut buf = GpuMdBuffer::new(n);
277 for i in 0..n {
278 buf.atoms[i].pos = [i as f32 * 1.5, 0.0, 0.0];
279 buf.atoms[i].mass = 1.0;
280 }
281 buf
282 }
283
284 #[test]
285 fn test_gpu_md_atom_fields() {
286 let a = GpuMdAtom {
287 pos: [1.0, 2.0, 3.0],
288 vel: [0.1, 0.2, 0.3],
289 force: [0.0; 3],
290 mass: 12.0,
291 charge: -0.5,
292 };
293 assert_eq!(a.mass, 12.0);
294 assert_eq!(a.charge, -0.5);
295 }
296
297 #[test]
298 fn test_gpu_md_params_fields() {
299 let p = default_params();
300 assert_eq!(p.n_atoms, 4);
301 assert!(p.cutoff > p.sigma);
302 }
303
304 #[test]
305 fn test_gpu_md_buffer_new() {
306 let buf = GpuMdBuffer::new(5);
307 assert_eq!(buf.len(), 5);
308 assert!(!buf.is_empty());
309 assert_eq!(buf.step, 0);
310 }
311
312 #[test]
313 fn test_gpu_md_buffer_empty() {
314 let buf = GpuMdBuffer::new(0);
315 assert!(buf.is_empty());
316 }
317
318 #[test]
319 fn test_lj_potential_minimum() {
320 let params = default_params();
321 let r_min = 2.0_f32.powf(1.0 / 6.0) * params.sigma;
323 let u = lj_potential_gpu(r_min, ¶ms);
324 assert!((u - (-params.epsilon)).abs() < 0.01);
326 }
327
328 #[test]
329 fn test_lj_potential_zero_beyond_cutoff() {
330 let params = default_params();
331 assert_eq!(lj_potential_gpu(params.cutoff + 0.1, ¶ms), 0.0);
332 }
333
334 #[test]
335 fn test_lj_potential_zero_near_zero_r() {
336 let params = default_params();
337 assert_eq!(lj_potential_gpu(0.0, ¶ms), 0.0);
338 }
339
340 #[test]
341 fn test_lj_force_repulsive_close() {
342 let params = default_params();
343 let f = lj_force_gpu(0.8, ¶ms);
345 assert!(f > 0.0);
346 }
347
348 #[test]
349 fn test_lj_force_attractive_far() {
350 let params = default_params();
351 let f = lj_force_gpu(1.2, ¶ms);
353 assert!(f < 0.0);
354 }
355
356 #[test]
357 fn test_lj_force_zero_beyond_cutoff() {
358 let params = default_params();
359 assert_eq!(lj_force_gpu(params.cutoff + 1.0, ¶ms), 0.0);
360 }
361
362 #[test]
363 fn test_pbc_distance_no_wrap() {
364 let params = default_params();
365 let a = [1.0, 0.0, 0.0];
366 let b = [2.0, 0.0, 0.0];
367 let d = pbc_distance_gpu(a, b, params.box_size);
368 assert!((d - 1.0).abs() < 1e-5);
369 }
370
371 #[test]
372 fn test_pbc_distance_wrap() {
373 let box_size = [10.0_f32; 3];
374 let a = [0.5, 0.0, 0.0];
375 let b = [9.5, 0.0, 0.0];
376 let d = pbc_distance_gpu(a, b, box_size);
378 assert!((d - 1.0).abs() < 1e-4);
379 }
380
381 #[test]
382 fn test_pbc_distance_self() {
383 let box_size = [10.0_f32; 3];
384 let a = [3.0, 4.0, 5.0];
385 let d = pbc_distance_gpu(a, a, box_size);
386 assert!(d < 1e-5);
387 }
388
389 #[test]
390 fn test_compute_forces_gpu_newton3() {
391 let mut buf = GpuMdBuffer::new(2);
392 buf.atoms[0].pos = [0.0; 3];
393 buf.atoms[1].pos = [1.2, 0.0, 0.0];
394 buf.atoms[0].mass = 1.0;
395 buf.atoms[1].mass = 1.0;
396 let params = default_params();
397 compute_forces_gpu(&mut buf, ¶ms);
398 assert!((buf.atoms[0].force[0] + buf.atoms[1].force[0]).abs() < 1e-5);
400 }
401
402 #[test]
403 fn test_compute_forces_gpu_zero_beyond_cutoff() {
404 let mut buf = GpuMdBuffer::new(2);
405 buf.atoms[0].pos = [0.0; 3];
406 buf.atoms[1].pos = [5.0, 0.0, 0.0]; let params = default_params();
408 compute_forces_gpu(&mut buf, ¶ms);
409 assert!(buf.atoms[0].force[0].abs() < 1e-8);
410 }
411
412 #[test]
413 fn test_verlet_integrate_gpu_position() {
414 let mut buf = GpuMdBuffer::new(1);
415 buf.atoms[0].vel = [1.0, 0.0, 0.0];
416 buf.atoms[0].force = [0.0; 3];
417 verlet_integrate_gpu(&mut buf, 0.1);
418 assert!((buf.atoms[0].pos[0] - 0.1).abs() < 1e-5);
419 }
420
421 #[test]
422 fn test_verlet_integrate_gpu_step_counter() {
423 let mut buf = GpuMdBuffer::new(1);
424 verlet_integrate_gpu(&mut buf, 0.01);
425 assert_eq!(buf.step, 1);
426 }
427
428 #[test]
429 fn test_kinetic_energy_gpu_zero_vel() {
430 let buf = make_buf_grid(4);
431 let ke = kinetic_energy_gpu(&buf);
432 assert!(ke.abs() < 1e-8);
433 }
434
435 #[test]
436 fn test_kinetic_energy_gpu_nonzero() {
437 let mut buf = GpuMdBuffer::new(2);
438 buf.atoms[0].vel = [1.0, 0.0, 0.0];
439 buf.atoms[0].mass = 2.0;
440 buf.atoms[1].vel = [0.0, 0.0, 0.0];
441 buf.atoms[1].mass = 1.0;
442 let ke = kinetic_energy_gpu(&buf);
443 assert!((ke - 1.0).abs() < 1e-5);
445 }
446
447 #[test]
448 fn test_potential_energy_gpu_empty() {
449 let buf = GpuMdBuffer::new(0);
450 let params = default_params();
451 assert_eq!(potential_energy_gpu(&buf, ¶ms), 0.0);
452 }
453
454 #[test]
455 fn test_potential_energy_gpu_single() {
456 let buf = GpuMdBuffer::new(1);
457 let params = default_params();
458 assert_eq!(potential_energy_gpu(&buf, ¶ms), 0.0);
460 }
461
462 #[test]
463 fn test_temperature_gpu_zero_vel() {
464 let buf = make_buf_grid(4);
465 let t = temperature_gpu(&buf);
466 assert!(t < 1e-6);
467 }
468
469 #[test]
470 fn test_temperature_gpu_empty() {
471 let buf = GpuMdBuffer::new(0);
472 assert_eq!(temperature_gpu(&buf), 0.0);
473 }
474
475 #[test]
476 fn test_temperature_gpu_nonzero() {
477 let mut buf = GpuMdBuffer::new(3);
478 for a in buf.atoms.iter_mut() {
479 a.vel = [1.0, 1.0, 1.0];
480 a.mass = 1.0;
481 }
482 let t = temperature_gpu(&buf);
483 assert!(t > 0.0);
484 }
485
486 #[test]
487 fn test_rescale_velocities_gpu() {
488 let mut buf = GpuMdBuffer::new(4);
489 for a in buf.atoms.iter_mut() {
490 a.vel = [1.0, 0.5, 0.2];
491 a.mass = 1.0;
492 }
493 let target = 300.0;
494 rescale_velocities_gpu(&mut buf, target);
495 let t_after = temperature_gpu(&buf);
496 assert!((t_after - target).abs() < 1.0);
497 }
498
499 #[test]
500 fn test_rescale_velocities_gpu_zero_vel_noop() {
501 let mut buf = GpuMdBuffer::new(2);
502 rescale_velocities_gpu(&mut buf, 300.0);
504 for a in &buf.atoms {
505 assert!(a.vel[0].abs() < 1e-8);
506 }
507 }
508
509 #[test]
510 fn test_buf_clone() {
511 let buf = make_buf_grid(3);
512 let buf2 = buf.clone();
513 assert_eq!(buf2.len(), 3);
514 }
515
516 #[test]
517 fn test_compute_forces_accumulate_many() {
518 let mut buf = make_buf_grid(4);
519 let params = default_params();
520 compute_forces_gpu(&mut buf, ¶ms);
521 let fx_total: f32 = buf.atoms.iter().map(|a| a.force[0]).sum();
523 assert!(fx_total.abs() < 1e-4);
524 }
525
526 #[test]
527 fn test_lj_potential_positive_repulsive() {
528 let params = default_params();
529 let u = lj_potential_gpu(0.5, ¶ms);
531 assert!(u > 0.0);
532 }
533
534 #[test]
535 fn test_verlet_integrate_gpu_velocity_from_force() {
536 let mut buf = GpuMdBuffer::new(1);
537 buf.atoms[0].force = [2.0, 0.0, 0.0];
538 buf.atoms[0].mass = 1.0;
539 verlet_integrate_gpu(&mut buf, 0.5);
540 assert!((buf.atoms[0].vel[0] - 1.0).abs() < 1e-5);
542 }
543
544 #[test]
545 fn test_pbc_distance_3d_wrap() {
546 let box_size = [5.0_f32; 3];
547 let a = [0.1, 0.1, 0.1];
548 let b = [4.9, 4.9, 4.9];
549 let d = pbc_distance_gpu(a, b, box_size);
550 let expected = 0.2 * 3.0_f32.sqrt();
552 assert!((d - expected).abs() < 1e-4);
553 }
554
555 #[test]
556 fn test_total_energy_two_atoms() {
557 let mut buf = GpuMdBuffer::new(2);
558 buf.atoms[0].pos = [0.0; 3];
559 buf.atoms[0].mass = 1.0;
560 buf.atoms[1].pos = [1.1, 0.0, 0.0]; buf.atoms[1].mass = 1.0;
562 let params = default_params();
563 let pe = potential_energy_gpu(&buf, ¶ms);
564 assert!(pe < 0.0);
566 }
567}