55 std::vector<int> atomic_numbers(number_of_atoms);
56 std::vector<Vector3D> cartesian_positions_bohr(number_of_atoms);
58 for (
int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
59 atomic_numbers[atom_index] = crystal_structure_.
atomic_number(atom_index);
61 throw std::runtime_error(
"D3(BJ) dispersion: no reference data for element '" +
62 crystal_structure_.
element(atom_index) +
63 "'. Supported elements: C, Si, Cr, Fe, Ni, Cu.");
71 cartesian_positions_bohr[atom_index] = fractional.
x * lattice_vectors_bohr[0] +
72 fractional.
y * lattice_vectors_bohr[1] +
73 fractional.
z * lattice_vectors_bohr[2];
76 std::vector<Real> coordination_numbers = compute_coordination_numbers(
77 atomic_numbers, cartesian_positions_bohr, lattice_vectors_bohr);
78 std::vector<Real> r2r4(number_of_atoms);
79 for (
int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
88 constexpr Real vdw_cutoff_squared_bohr = 9000.0;
89 const std::vector<LatticeImageShift> vdw_image_shifts =
90 enumerate_image_shifts(lattice_vectors_bohr, vdw_cutoff_squared_bohr);
94 for (
int atom_i = 0; atom_i < number_of_atoms; atom_i++) {
95 for (
int atom_j = atom_i; atom_j < number_of_atoms; atom_j++) {
96 Real c6 = interpolate_c6(atomic_numbers[atom_i],
97 atomic_numbers[atom_j],
98 coordination_numbers[atom_i],
99 coordination_numbers[atom_j]);
100 Real c8 = 3.0 * c6 * r2r4[atom_i] * r2r4[atom_j];
101 Real damping_radius = becke_johnson_damping_radius(r2r4[atom_i], r2r4[atom_j]);
102 Real damping_radius_6 = ipow(damping_radius, 6);
103 Real damping_radius_8 = ipow(damping_radius, 8);
106 cartesian_positions_bohr[atom_i] - cartesian_positions_bohr[atom_j];
107 bool is_self_pair = (atom_i == atom_j);
113 Real weight = is_self_pair ? 0.5 : 1.0;
115 for (
const LatticeImageShift& image : vdw_image_shifts) {
116 if (is_self_pair && image.is_origin) {
119 Vector3D image_separation_bohr = separation_bohr + image.shift_bohr;
120 Real distance_squared_bohr = image_separation_bohr.
x * image_separation_bohr.
x +
121 image_separation_bohr.
y * image_separation_bohr.
y +
122 image_separation_bohr.
z * image_separation_bohr.
z;
123 if (distance_squared_bohr > vdw_cutoff_squared_bohr) {
126 Real distance_6_bohr = ipow(distance_squared_bohr, 3);
127 Real distance_8_bohr = distance_6_bohr * distance_squared_bohr;
128 energy_6 += weight * c6 / (distance_6_bohr + damping_radius_6);
129 energy_8 += weight * c8 / (distance_8_bohr + damping_radius_8);
134 constexpr Real s6 = 1.0;
135 constexpr Real s8 = 0.7875;
136 constexpr Real hartree_to_rydberg = 2.0;
137 return -(s6 * energy_6 + s8 * energy_8) * hartree_to_rydberg;
373 std::vector<int> atomic_numbers(number_of_atoms);
374 std::vector<Vector3D> cartesian_positions_bohr(number_of_atoms);
376 for (
int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
377 atomic_numbers[atom_index] = crystal_structure_.
atomic_number(atom_index);
379 throw std::runtime_error(
"D3(BJ) dispersion: no reference data for element '" +
380 crystal_structure_.
element(atom_index) +
381 "'. Supported elements: C, Si, Cr, Fe, Ni, Cu.");
384 cartesian_positions_bohr[atom_index] = fractional.
x * lattice_vectors_bohr[0] +
385 fractional.
y * lattice_vectors_bohr[1] +
386 fractional.
z * lattice_vectors_bohr[2];
389 std::vector<Real> coordination_numbers = compute_coordination_numbers(
390 atomic_numbers, cartesian_positions_bohr, lattice_vectors_bohr);
391 std::vector<Real> r2r4(number_of_atoms);
392 for (
int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
396 constexpr Real vdw_cutoff_squared_bohr = 9000.0;
397 const std::vector<LatticeImageShift> vdw_image_shifts =
398 enumerate_image_shifts(lattice_vectors_bohr, vdw_cutoff_squared_bohr);
400 constexpr Real s6 = 1.0;
401 constexpr Real s8 = 0.7875;
402 constexpr Real hartree_to_rydberg = 2.0;
404 std::vector<Vector3D> forces(number_of_atoms, {0.0, 0.0, 0.0});
405 Real stress_raw[3][3] = {};
410 std::vector<Real> coordination_number_energy_derivative(number_of_atoms, 0.0);
413 for (
int atom_i = 0; atom_i < number_of_atoms; atom_i++) {
414 for (
int atom_j = atom_i; atom_j < number_of_atoms; atom_j++) {
415 C6InterpolationResult c6_result =
416 interpolate_c6_and_derivatives(atomic_numbers[atom_i],
417 atomic_numbers[atom_j],
418 coordination_numbers[atom_i],
419 coordination_numbers[atom_j]);
420 Real c6 = c6_result.value;
421 Real c8 = 3.0 * c6 * r2r4[atom_i] * r2r4[atom_j];
422 Real r2r4_product_times_three = 3.0 * r2r4[atom_i] * r2r4[atom_j];
423 Real damping_radius = becke_johnson_damping_radius(r2r4[atom_i], r2r4[atom_j]);
424 Real damping_radius_6 = ipow(damping_radius, 6);
425 Real damping_radius_8 = ipow(damping_radius, 8);
428 cartesian_positions_bohr[atom_i] - cartesian_positions_bohr[atom_j];
429 bool is_self_pair = (atom_i == atom_j);
430 Real weight = is_self_pair ? 0.5 : 1.0;
433 Real pair_derivative_with_respect_to_cn_i = 0.0;
434 Real pair_derivative_with_respect_to_cn_j = 0.0;
436 for (
const LatticeImageShift& image : vdw_image_shifts) {
437 if (is_self_pair && image.is_origin) {
440 Vector3D image_separation_bohr = separation_bohr + image.shift_bohr;
441 Real distance_squared_bohr = image_separation_bohr.
x * image_separation_bohr.
x +
442 image_separation_bohr.
y * image_separation_bohr.
y +
443 image_separation_bohr.
z * image_separation_bohr.
z;
444 if (distance_squared_bohr > vdw_cutoff_squared_bohr) {
447 Real distance_6_bohr = ipow(distance_squared_bohr, 3);
448 Real distance_8_bohr = distance_6_bohr * distance_squared_bohr;
449 Real denominator_6 = distance_6_bohr + damping_radius_6;
450 Real denominator_8 = distance_8_bohr + damping_radius_8;
452 pair_derivative_with_respect_to_cn_i +=
453 weight * (s6 * c6_result.derivative_with_respect_to_coordination_number_i /
455 s8 * r2r4_product_times_three *
456 c6_result.derivative_with_respect_to_coordination_number_i /
458 pair_derivative_with_respect_to_cn_j +=
459 weight * (s6 * c6_result.derivative_with_respect_to_coordination_number_j /
461 s8 * r2r4_product_times_three *
462 c6_result.derivative_with_respect_to_coordination_number_j /
469 Real distance_squared_power_2 = distance_squared_bohr * distance_squared_bohr;
470 Real direct_coefficient =
471 hartree_to_rydberg * weight *
472 (6.0 * s6 * c6 * distance_squared_power_2 /
473 (denominator_6 * denominator_6) +
474 8.0 * s8 * c8 * distance_6_bohr / (denominator_8 * denominator_8));
483 Vector3D pair_force_on_i = (-direct_coefficient) * image_separation_bohr;
484 forces[atom_i] += pair_force_on_i;
485 forces[atom_j] += (-pair_force_on_i);
487 for (
int direction_a = 0; direction_a < 3; direction_a++) {
488 for (
int direction_b = 0; direction_b < 3; direction_b++) {
489 stress_raw[direction_a][direction_b] +=
490 direct_coefficient * image_separation_bohr[direction_a] *
491 image_separation_bohr[direction_b];
495 coordination_number_energy_derivative[atom_i] +=
496 -hartree_to_rydberg * pair_derivative_with_respect_to_cn_i;
497 coordination_number_energy_derivative[atom_j] +=
498 -hartree_to_rydberg * pair_derivative_with_respect_to_cn_j;
506 constexpr Real cn_cutoff_squared_bohr = 1600.0;
507 const std::vector<LatticeImageShift> cn_image_shifts =
508 enumerate_image_shifts(lattice_vectors_bohr, cn_cutoff_squared_bohr);
509 std::vector<Real> covalent_radii(number_of_atoms);
510 for (
int atom_index = 0; atom_index < number_of_atoms; atom_index++) {
511 covalent_radii[atom_index] =
514 for (
int atom_i = 0; atom_i < number_of_atoms; atom_i++) {
515 Real energy_derivative_i = coordination_number_energy_derivative[atom_i];
516 for (
int atom_j = 0; atom_j < number_of_atoms; atom_j++) {
517 Real covalent_radius_sum = covalent_radii[atom_i] + covalent_radii[atom_j];
519 cartesian_positions_bohr[atom_j] - cartesian_positions_bohr[atom_i];
520 for (
const LatticeImageShift& image : cn_image_shifts) {
521 if (atom_i == atom_j && image.is_origin) {
524 Vector3D image_separation_bohr = separation_bohr + image.shift_bohr;
525 Real distance_squared_bohr = image_separation_bohr.
x * image_separation_bohr.
x +
526 image_separation_bohr.
y * image_separation_bohr.
y +
527 image_separation_bohr.
z * image_separation_bohr.
z;
528 if (distance_squared_bohr > cn_cutoff_squared_bohr) {
531 Real distance_bohr = std::sqrt(distance_squared_bohr);
532 Real ratio = covalent_radius_sum / distance_bohr;
533 Real logistic_argument = -k1_ * (ratio - 1.0);
534 Real logistic_value = 1.0 / (1.0 + std::exp(logistic_argument));
539 Real counting_function_derivative =
540 -k1_ * (ratio / distance_bohr) * logistic_value * (1.0 - logistic_value);
543 energy_derivative_i * counting_function_derivative / distance_bohr;
544 Vector3D gradient_contribution = coefficient * image_separation_bohr;
545 forces[atom_i] += gradient_contribution;
546 forces[atom_j] += (-gradient_contribution);
548 for (
int direction_a = 0; direction_a < 3; direction_a++) {
549 for (
int direction_b = 0; direction_b < 3; direction_b++) {
550 stress_raw[direction_a][direction_b] +=
551 coefficient * image_separation_bohr[direction_a] *
552 image_separation_bohr[direction_b];
561 for (
int direction_a = 0; direction_a < 3; direction_a++) {
562 for (
int direction_b = 0; direction_b < 3; direction_b++) {
563 stress[direction_a][direction_b] =
564 -stress_raw[direction_a][direction_b] / cell_volume_bohr_cubed;
568 return {forces, stress};